commit 336a19762a12c05f62e575a35f97c8dc9a5e45ae Author: Quella777 <2892744389@qq.com> Date: Fri Jan 9 13:59:10 2026 +0800 init diff --git a/DeviceActivate/NetraLib/README.md b/DeviceActivate/NetraLib/README.md new file mode 100644 index 0000000..c680108 --- /dev/null +++ b/DeviceActivate/NetraLib/README.md @@ -0,0 +1,128 @@ +# NetraLib +c/c++基本开发库 + +# TCP 服务端操作 +包括多线程客户端连接,指定客户端数据的收发等等功能 + +# Linux 中屏蔽所有信号操作 +屏蔽所有信号,以防止意外退出 + + +# 写文件操作 +允许原文本进行覆盖写,追加写 +允许二进制进行覆盖写,追加写 +允许在特定位置后面进行插入覆盖操作 +允许删除特定字段后面所有内容在进行写操作 +可以根据需要计算特定符号最后一个字节或者第一个字节所在位置所在位置 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 读文件操作 +支持全文读取(文本和二进制模式) +支持按行读取文本内容 +支持按指定字节数读取数据 +支持计算第一个指定字节序列结束位置(包含该序列本身)的字节数 +提供文件是否存在和文件大小查询 +支持重置文件读取位置,实现多次读取 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 字符串操作 +支持左右空格删除 +支持格式化输出 + + +# Http请求 +提供基于 `cpp-httplib` 的简易 HTTP 客户端封装 `NetRequest`,支持: +1. 同步/异步 GET、POST(JSON、表单) +2. 连接/读写超时设置、Keep-Alive +3. 并发请求上限控制 +4. 可选内存缓存(GET 命中时不发起网络请求) +5. 简单日志回调与性能统计 +6. 断点续传下载到本地文件 + +使用步骤: + +1) 引入头文件,配置目标地址 +```cpp +#include "NetRequest.hpp" + +ntq::RequestOptions opt; +opt.scheme = "http"; // 或 https +opt.host = "127.0.0.1"; // 服务器地址 +opt.port = 8080; // 端口(https 一般 443) +opt.base_path = "/api"; // 可选统一前缀 +opt.connect_timeout_ms = 3000; +opt.read_timeout_ms = 8000; +opt.write_timeout_ms = 8000; +opt.default_headers = { {"Authorization", "Bearer TOKEN"} }; // 可选 + +ntq::NetRequest req(opt); +req.setMaxConcurrentRequests(4); +req.enableCache(std::chrono::seconds(10)); +``` + +2) 发送 GET 请求 +```cpp +auto r = req.Get("/info"); +if (r && r->status == 200) { + // r->body 为返回内容 +} + +// 带查询参数与额外请求头 +httplib::Params q = {{"q","hello"},{"page","1"}}; +httplib::Headers h = {{"X-Req-Id","123"}}; +auto r2 = req.Get("/search", q, h); +``` + +3) 发送 POST 请求 +```cpp +// JSON(Content-Type: application/json) +std::string json = R"({"name":"orangepi","mode":"demo"})"; +auto p1 = req.PostJson("/set", json); + +// 表单(application/x-www-form-urlencoded) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = req.PostForm("/login", form); +``` + +4) 异步调用 +```cpp +auto fut = req.GetAsync("/info"); +auto res = fut.get(); // 与同步用法一致 +``` + +5) 下载到本地(支持断点续传) +```cpp +bool ok = req.DownloadToFile("/files/pkg.bin", "/tmp/pkg.bin", {}, /*resume*/true); +``` + +6) 统计信息 +```cpp +auto s = req.getStats(); +// s.total_requests / s.total_errors / s.last_latency_ms / s.avg_latency_ms +``` + +说明: +- 若使用 HTTPS,需在编译时添加 `-DCPPHTTPLIB_OPENSSL_SUPPORT` 并链接 `-lssl -lcrypto`,且将 `opt.scheme` 设为 `"https"`、端口通常为 `443`。 +- `base_path` 与各函数传入的 `path` 会自动合并,例如 `base_path="/api"` 且 `Get("/info")` 实际请求路径为 `/api/info`。 + +7) 快速用法(无需实例化) +```cpp +using ntq::NetRequest; + +// 直接传完整 URL 发起 GET +auto g = NetRequest::QuickGet("http://127.0.0.1:8080/api/info"); + +// 直接传完整 URL 发起 POST(JSON) +auto p1 = NetRequest::QuickPostJson( + "http://127.0.0.1:8080/api/set", + R"({"name":"orangepi","mode":"demo"})" +); + +// 直接传完整 URL 发起 POST(表单) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = NetRequest::QuickPostForm("http://127.0.0.1:8080/login", form); +``` diff --git a/DeviceActivate/NetraLib/include/NetRequest.hpp b/DeviceActivate/NetraLib/include/NetRequest.hpp new file mode 100644 index 0000000..3cb473c --- /dev/null +++ b/DeviceActivate/NetraLib/include/NetRequest.hpp @@ -0,0 +1,344 @@ +/* +本文件 +网络请求类需要实现以下功能: +1. 发送网络请求 +2. 接收网络响应 +3. 处理网络请求和响应 +4. 实现网络请求和响应的回调函数 +5. 实现网络请求和响应的错误处理 +6. 实现网络请求和响应的日志记录 +7. 实现网络请求和响应的性能统计 +8. 实现网络请求和响应的并发控制 +9. 实现网络请求和响应的缓存管理 +10. 实现网络请求和响应的断点续传 +11. 实现网络请求和响应的断点续传 +12. 实现网络请求和响应的断点续传 +13. 实现网络请求和响应的断点续传 +14. 实现网络请求和响应的断点续传 +*/ +#pragma once +#include "httplib.h" +#include +#include +#include +#include + +// C++17/14 可选类型回退适配:统一使用 ntq::optional / ntq::nullopt +#if defined(__has_include) + #if __has_include() + #include + // 仅当启用了 C++17 或库声明了 optional 功能时,才使用 std::optional + #if defined(__cpp_lib_optional) || (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif +#else + // 无 __has_include:按语言级别判断 + #if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + #include + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #else + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #endif +#endif + +namespace ntq +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief HTTP 响应对象 + * + * 用于承载一次请求返回的状态码、正文与响应头; + * 当 from_cache 为 true 时,表示该响应来自本地缓存(而非实时网络)。 + */ + struct HttpResponse + { + int status = 0; ///< HTTP 状态码(例如 200, 404 等) + std::string body; ///< 响应正文 + httplib::Headers headers; ///< 响应头(大小写不敏感) + bool from_cache = false; ///< 是否来自缓存 + }; + + /** + * @brief 错误码定义 + */ + enum class ErrorCode + { + None = 0, ///< 无错误 + Network, ///< 网络错误(连接失败/发送失败/接收失败等) + Timeout, ///< 超时 + Canceled, ///< 被取消 + InvalidURL, ///< URL 非法 + IOError, ///< 本地 IO 错误(如写文件失败) + SSL, ///< SSL/HTTPS 相关错误 + Unknown ///< 未分类错误 + }; + + /** + * @brief 请求级别的参数配置 + * + * 包含基础连接信息(协议、主机、端口)与默认头部、超时设置等。 + */ + struct RequestOptions + { + std::string scheme = "http"; ///< 协议:http 或 https + std::string host; ///< 目标主机名或 IP(必填) + int port = 80; ///< 端口,http 默认为 80,https 通常为 443 + std::string base_path; ///< 可选的统一前缀(例如 "/api/v1") + + int connect_timeout_ms = 5000; ///< 连接超时(毫秒) + int read_timeout_ms = 10000; ///< 读取超时(毫秒) + int write_timeout_ms = 10000; ///< 写入超时(毫秒) + bool keep_alive = true; ///< 是否保持连接(Keep-Alive) + + httplib::Headers default_headers; ///< 默认头部,随所有请求发送 + }; + + /** + * @class NetRequest + * @brief HTTP 客户端封装(基于 cpp-httplib) + * + * 提供同步和异步的 GET/POST 请求、并发限制、可选缓存、日志回调、 + * 性能统计以及断点续传下载到文件等能力。 + */ + class NetRequest + { + public: + using LogCallback = std::function; ///< 日志回调类型 + + /** + * @brief 运行时统计数据 + */ + struct Stats + { + uint64_t total_requests = 0; ///< 累计请求次数 + uint64_t total_errors = 0; ///< 累计失败次数 + double last_latency_ms = 0.0;///< 最近一次请求耗时(毫秒) + double avg_latency_ms = 0.0; ///< 指数平滑后的平均耗时(毫秒) + }; + + /** + * @brief 构造函数 + * @param options 请求参数配置(目标地址、超时、默认头部等) + */ + explicit NetRequest(const RequestOptions &options); + /** + * @brief 析构函数 + */ + ~NetRequest(); + + /** + * @brief 设置日志回调 + * @param logger 回调函数,接受一行日志字符串 + */ + void setLogger(LogCallback logger); + /** + * @brief 设置最大并发请求数 + * @param n 并发上限(最小值为 1) + */ + void setMaxConcurrentRequests(size_t n); + /** + * @brief 启用内存缓存 + * @param ttl 缓存有效期(超时后自动失效) + */ + void enableCache(std::chrono::milliseconds ttl); + /** + * @brief 禁用并清空缓存 + */ + void disableCache(); + + /** + * @brief 发送同步 GET 请求 + * @param path 资源路径(会与 base_path 合并) + * @param query 查询参数(会拼接为 ?k=v&...) + * @param headers 额外请求头(与默认头部合并) + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional Get(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST JSON 请求 + * @param path 资源路径 + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST 表单请求(application/x-www-form-urlencoded) + * @param path 资源路径 + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 GET 请求 + * @return std::future,用于获取响应结果 + */ + std::future> GetAsync(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST JSON 请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST 表单请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 下载文件到本地,支持断点续传 + * @param path 资源路径 + * @param local_file 本地保存路径 + * @param headers 额外头部 + * @param resume 是否启用续传(Range) + * @param chunk_size 预留参数:分块大小(当前由 httplib 内部回调驱动) + * @param err 可选错误码输出 + * @return true 下载成功(200 或 206),false 失败 + */ + bool DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers = {}, + bool resume = true, + size_t chunk_size = 1 << 15, + ErrorCode *err = nullptr); + + /** + * @brief 获取统计数据快照 + */ + Stats getStats() const; + + /** + * @brief 便捷:直接用完整 URL 发起 GET(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickGet(const std::string &url, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST JSON(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST 表单(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + private: + struct Impl; + Impl *impl_; + }; +} \ No newline at end of file diff --git a/DeviceActivate/NetraLib/include/Netra.hpp b/DeviceActivate/NetraLib/include/Netra.hpp new file mode 100644 index 0000000..362ae28 --- /dev/null +++ b/DeviceActivate/NetraLib/include/Netra.hpp @@ -0,0 +1,426 @@ +#pragma once +#include "QCL_Include.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @class TcpServer + * @brief 简单的多线程TCP服务器类,支持多个客户端连接,数据收发及断开处理 + * + * 该类使用一个线程专门用于监听客户端连接, + * 每当有客户端连接成功时,为其创建一个独立线程处理该客户端的数据收发。 + * 线程安全地管理所有客户端Socket句柄。 + */ + class TcpServer + { + public: + /** + * @brief 构造函数,指定监听端口 + * @param port 服务器监听端口号 + */ + TcpServer(int port); + + /** + * @brief 析构函数,自动调用 stop() 停止服务器并清理资源 + */ + ~TcpServer(); + + /** + * @brief 启动服务器,创建监听socket,开启监听线程 + * @return 启动成功返回true,失败返回false + */ + bool start(); + + /** + * @brief 停止服务器,关闭所有连接,释放资源,等待所有线程退出 + */ + void stop(); + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端Socket描述符 + * @param message 发送的字符串消息 + */ + void sendToClient(int clientSock, const std::string &message); + + /** + * @brief 从指定客户端接收数据(单次调用) + * @param clientSock 客户端Socket描述符 + * @param flag:false 非阻塞模式,true 阻塞模式 + */ + std::string receiveFromClient(int clientSock, bool flag = true); + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *getClientIPAndPort(int clientSock); + + /** + * @brief 获取当前所有已连接客户端Socket的副本 + * @return 包含所有客户端Socket的vector,线程安全 + */ + std::vector getClientSockets(); + + /** + * @brief 从服务器的客户端列表中移除并关闭一个客户端socket + * @param clientSock 客户端Socket描述符 + */ + void removeClient(int clientSock); + + /** + * @brief 非阻塞探测客户端是否已断开(不消耗数据) + * @param clientSock 客户端Socket描述符 + * @return true 已断开或发生致命错误;false 仍然存活或暂无数据 + */ + bool isClientDisconnected(int clientSock); + + private: + /** + * @brief 监听并接受新的客户端连接(运行在独立线程中) + */ + void acceptClients(); + + private: + int serverSock_; ///< 服务器监听Socket描述符 + int port_; ///< 服务器监听端口 + std::atomic running_; ///< 服务器运行状态标志(线程安全) + std::vector clientThreads_; ///< 用于处理每个客户端的线程集合 + std::thread acceptThread_; ///< 负责监听新连接的线程 + std::mutex clientsMutex_; ///< 保护clientSockets_的互斥锁 + std::vector clientSockets_; ///< 当前所有连接的客户端Socket集合 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief 文件写入工具类(线程安全) + * + * 该类支持多种文件写入方式: + * - 覆盖写文本 + * - 追加写文本 + * - 按位置覆盖原文写入 + * - 二进制覆盖写 + * - 二进制追加写 + * + * 特点: + * - 文件不存在时自动创建 + * - 支持覆盖和追加两种模式 + * - 支持二进制模式,适合写入非文本数据 + * - 内部使用 std::mutex 实现线程安全 + */ + class WriteFile + { + public: + /** + * @brief 构造函数 + * @param filePath 文件路径 + */ + explicit WriteFile(const std::string &filePath); + + /** + * @brief 覆盖写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteText(const std::string &content); + + /** + * @brief 追加写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendText(const std::string &content); + + /** + * @brief 覆盖写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteBinary(const std::vector &data); + + /** + * @brief 追加写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendBinary(const std::vector &data); + + /** + * @brief 计算第一个指定字节序列前的字节数 + * @param pattern 要查找的字节序列 + * @param includePattern true 表示返回值包含 pattern 自身长度,false 表示不包含 + * @return size_t 字节数,如果文件没打开或 pattern 为空则返回 0 + */ + size_t countBytesPattern(const std::string &pattern, bool includePattern = false); + + /** + * @brief 在文件中查找指定字节序列并在其后写入内容,如果不存在则追加到文件末尾 + * @param pattern 要查找的字节序列 + * @param content 要写入的内容 + * @return true 写入成功,false 文件打开失败 + * + * 功能说明: + * 1. 若文件中存在 pattern,则删除 pattern 之后的所有内容,并在其后插入 content。 + * 2. 若文件中不存在 pattern,则在文件末尾追加 content,若末尾无换行符则先补充换行。 + */ + bool writeAfterPatternOrAppend(const std::string &pattern, const std::string &content); + + /** + * @brief 在文件指定位置之后插入内容 + * @param content 要插入的内容 + * @param pos 插入位置(从文件开头算起的字节偏移量) + * @param length 插入的长度(>= content.size() 时,多余部分用空字节填充;< content.size() 时只截取前 length 个字节) + * @return true 插入成功,false 文件打开失败或参数不合法 + * + * 功能说明: + * 1. 不会覆盖原有数据,而是将 pos 之后的内容整体向后移动 length 个字节。 + * 2. 如果 length > content.size(),则在 content 后补充 '\0'(或空格,可按需求改)。 + * 3. 如果 length < content.size(),则只写入 content 的前 length 个字节。 + * 4. 文件整体大小会增加 length 个字节。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * insertAfterPos("XY", 2, 3) // 在索引 2 后插入 + * 结果: "ABX Y\0CDEFG" (这里 \0 代表补充的空字节) + */ + bool insertAfterPos(const std::string &content, size_t pos, size_t length); + + /** + * @brief 在文件指定位置覆盖写入内容 + * @param content 要写入的内容 + * @param pos 覆盖起始位置(从文件开头算起的字节偏移量) + * @param length 覆盖长度 + * @return true 覆盖成功,false 文件打开失败或 pos 越界 + * + * 功能说明: + * 1. 从 pos 开始覆盖 length 个字节,不会移动或增加文件大小。 + * 2. 如果 content.size() >= length,则只写入前 length 个字节。 + * 3. 如果 content.size() < length,则写入 content,并用 '\0' 补齐至 length。 + * 4. 如果 pos + length 超过文件末尾,则只覆盖到文件尾部,不会越界。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * overwriteAtPos("XY", 2, 3) + * 结果: "ABXYEFG" (原 "CDE" 被 "XY\0" 覆盖,\0 实际不可见) + */ + bool overwriteAtPos(const std::string &content, size_t pos, size_t length); + + void close(); + + private: + std::string filePath_; ///< 文件路径 + std::mutex writeMutex_; ///< 线程锁,保证多线程写入安全 + + /** + * @brief 通用文本写入接口(线程安全) + * @param content 要写入的内容 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeToFile(const std::string &content, std::ios::openmode mode); + + /** + * @brief 通用二进制写入接口(线程安全) + * @param data 要写入的二进制数据 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeBinary(const std::vector &data, std::ios::openmode mode); + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief ReadFile 类 - 读文件操作工具类 + * + * 功能: + * 1. 读取全文(文本 / 二进制) + * 2. 按行读取 + * 3. 按字节数读取 + * 4. 获取指定字节序列前的字节数(包含该字节序列) + * 5. 检查文件是否存在 + * 6. 获取文件大小 + * + * 设计: + * - 与 WriteFile 类风格保持一致 + * - 支持文本文件与二进制文件 + * - 自动关闭文件(析构时) + * - 内部使用 std::mutex 实现线程安全 + */ + class ReadFile + { + public: + /** + * @brief 构造函数 + * @param filename 文件路径 + */ + explicit ReadFile(const std::string &filename); + + /** + * @brief 析构函数,自动关闭文件 + */ + ~ReadFile(); + + /** + * @brief 打开文件(以二进制方式) + * @return true 打开成功 + * @return false 打开失败 + */ + bool Open(); + + /** + * @brief 关闭文件 + */ + void Close(); + + /** + * @brief 文件是否已经打开 + */ + bool IsOpen() const; + + /** + * @brief 读取全文(文本模式) + * @return 文件内容字符串 + */ + std::string ReadAllText(); + + /** + * @brief 读取全文(二进制模式) + * @return 文件内容字节数组 + */ + std::vector ReadAllBinary(); + + /** + * @brief 按行读取文本 + * @return 每行作为一个字符串的 vector + */ + std::vector ReadLines(); + + /** + * @brief 读取指定字节数 + * @param count 要读取的字节数 + * @return 实际读取到的字节数据 + */ + std::vector ReadBytes(size_t count); + + /** + * @brief 获取指定字节序列前的字节数(包含该字节序列) + * @param marker 要查找的字节序列(可能不止一个字节) + * @return 如果找到,返回前面部分字节数;找不到返回0 + */ + size_t GetBytesBefore(const std::string &marker, bool includeMarker = false); + + /** + * @brief 从指定位置读取指定字节数,默认读取到文件末尾 + * @param pos 起始位置(字节偏移) + * @param count 要读取的字节数,默认为0表示读取到文件末尾 + * @return 读取到的字节数据 + */ + std::vector ReadBytesFrom(size_t pos, size_t count = 0); + + /** + * @brief 检查文件是否存在 + */ + bool FileExists() const; + + /** + * @brief 获取文件大小(字节数) + */ + size_t GetFileSize() const; + + /** + * @brief 重置读取位置到文件开头 + */ + void Reset(); + + private: + std::string filename_; // 文件路径 + std::ifstream file_; // 文件流对象 + mutable std::mutex mtx_; // 可变,保证 const 方法也能加锁 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals(); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 字符串操作 + // 去除字符串的左空格 + std::string Ltrim(const std::string &s); + + // 去除字符串右侧的空格 + std::string Rtrim(const std::string &s); + + // 去除字符串左右两侧的空格 + std::string LRtrim(const std::string &s); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // c++进行格式化输出 + // 通用类型转字符串 + template + std::string to_string_any(const T &value) + { + std::ostringstream oss; + oss << value; + return oss.str(); + } + + // 递归获取 tuple 中 index 对应参数 + template + std::string get_tuple_arg(const Tuple &tup, std::size_t index) + { + if constexpr (I < std::tuple_size_v) + { + if (I == index) + return to_string_any(std::get(tup)); + else + return get_tuple_arg(tup, index); + } + else + { + throw std::runtime_error("Too few arguments for format string"); + } + } + + // format 函数 + template + std::string format(const std::string &fmt, const Args &...args) + { + std::ostringstream oss; + std::tuple tup(args...); + size_t pos = 0; + size_t arg_idx = 0; + + while (pos < fmt.size()) + { + if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '{') + { + oss << '{'; + pos += 2; + } + else if (fmt[pos] == '}' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << '}'; + pos += 2; + } + else if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << get_tuple_arg(tup, arg_idx++); + pos += 2; + } + else + { + oss << fmt[pos++]; + } + } + + if (arg_idx < sizeof...(Args)) + throw std::runtime_error("Too many arguments for format string"); + + return oss.str(); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +} diff --git a/DeviceActivate/NetraLib/include/QCL_Include.hpp b/DeviceActivate/NetraLib/include/QCL_Include.hpp new file mode 100644 index 0000000..50a35cd --- /dev/null +++ b/DeviceActivate/NetraLib/include/QCL_Include.hpp @@ -0,0 +1,55 @@ +/** + * @file qcl_include.hpp + * @brief 通用C++开发头文件集合(Linux环境) + * @note 使用前确保目标平台支持C++17标准 + */ +#ifndef QCL_INCLUDE_HPP +#define QCL_INCLUDE_HPP +#pragma onece +// ==================== C/C++基础运行时库 ==================== +#include // 标准输入输出流(cin/cout/cerr) +#include // std::string类及相关操作 +#include // C风格字符串操作(strcpy/strcmp等) +#include // 通用工具函数(atoi/rand/malloc等) +#include // C风格IO(printf/scanf) +#include // 断言宏(调试期检查) +#include // 数学函数(sin/pow等) +#include // 时间处理(time/clock) +#include // 信号处理(signal/kill) +#include // 智能指针 + +// ==================== STL容器与算法 ==================== +#include // 动态数组(连续内存容器) +#include // 双向链表 +#include // 双端队列 +#include // 有序键值对(红黑树实现) +#include // 有序集合 +#include // 哈希表实现的键值对 +#include // 哈希表实现的集合 +#include // 栈适配器(LIFO) +#include // 队列适配器(FIFO) +#include // 通用算法(sort/find等) +#include // 数值算法(accumulate等) +#include // 迭代器相关 + +// ==================== 字符串与流处理 ==================== +#include // 字符串流(内存IO) +#include // 文件流(文件IO) +#include // 流格式控制(setw/setprecision) +#include // 正则表达式 +#include // 文件系统(C++17) +#include + +// ==================== 并发编程支持 ==================== +#include // 线程管理(std::thread) +#include // 互斥锁(mutex/lock_guard) +#include // 原子操作(线程安全变量) +#include // 条件变量(线程同步) + +// ==================== Linux网络编程 ==================== +#include // 套接字基础API(socket/bind) +#include // IPV4/IPV6地址结构体 +#include // 地址转换函数(inet_pton等) +#include // POSIX API(close/read/write) + +#endif // QCL_INCLUDE_HPP diff --git a/DeviceActivate/NetraLib/include/README.md b/DeviceActivate/NetraLib/include/README.md new file mode 100644 index 0000000..984ab89 --- /dev/null +++ b/DeviceActivate/NetraLib/include/README.md @@ -0,0 +1,850 @@ +cpp-httplib +=========== + +[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions) + +A C++11 single-file header-only cross platform HTTP/HTTPS library. + +It's extremely easy to setup. Just include the **httplib.h** file in your code! + +NOTE: This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. + +Simple examples +--------------- + +#### Server (Multi-threaded) + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Server svr; + +// HTTPS +httplib::SSLServer svr; + +svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { + res.set_content("Hello World!", "text/plain"); +}); + +svr.listen("0.0.0.0", 8080); +``` + +#### Client + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co"); + +// HTTPS +httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co"); + +auto res = cli.Get("/hi"); +res->status; +res->body; +``` + +SSL Support +----------- + +SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. + +NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0. + +NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`. + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// Server +httplib::SSLServer svr("./cert.pem", "./key.pem"); + +// Client +httplib::Client cli("https://localhost:1234"); // scheme + host +httplib::SSLClient cli("localhost:1234"); // host +httplib::SSLClient cli("localhost", 1234); // host, port + +// Use your CA bundle +cli.set_ca_cert_path("./ca-bundle.crt"); + +// Disable cert verification +cli.enable_server_certificate_verification(false); +``` + +NOTE: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. + +Server +------ + +```c++ +#include + +int main(void) +{ + using namespace httplib; + + Server svr; + + svr.Get("/hi", [](const Request& req, Response& res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { + auto numbers = req.matches[1]; + res.set_content(numbers, "text/plain"); + }); + + svr.Get("/body-header-param", [](const Request& req, Response& res) { + if (req.has_header("Content-Length")) { + auto val = req.get_header_value("Content-Length"); + } + if (req.has_param("key")) { + auto val = req.get_param_value("key"); + } + res.set_content(req.body, "text/plain"); + }); + + svr.Get("/stop", [&](const Request& req, Response& res) { + svr.stop(); + }); + + svr.listen("localhost", 1234); +} +``` + +`Post`, `Put`, `Delete` and `Options` methods are also supported. + +### Bind a socket to multiple interfaces and any available port + +```cpp +int port = svr.bind_to_any_port("0.0.0.0"); +svr.listen_after_bind(); +``` + +### Static File Server + +```cpp +// Mount / to ./www directory +auto ret = svr.set_mount_point("/", "./www"); +if (!ret) { + // The specified base directory doesn't exist... +} + +// Mount /public to ./www directory +ret = svr.set_mount_point("/public", "./www"); + +// Mount /public to ./www1 and ./www2 directories +ret = svr.set_mount_point("/public", "./www1"); // 1st order to search +ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search + +// Remove mount / +ret = svr.remove_mount_point("/"); + +// Remove mount /public +ret = svr.remove_mount_point("/public"); +``` + +```cpp +// User defined file extension and MIME type mappings +svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); +``` + +The followings are built-in mappings: + +| Extension | MIME Type | Extension | MIME Type | +| :--------- | :-------------------------- | :--------- | :-------------------------- | +| css | text/css | mpga | audio/mpeg | +| csv | text/csv | weba | audio/webm | +| txt | text/plain | wav | audio/wave | +| vtt | text/vtt | otf | font/otf | +| html, htm | text/html | ttf | font/ttf | +| apng | image/apng | woff | font/woff | +| avif | image/avif | woff2 | font/woff2 | +| bmp | image/bmp | 7z | application/x-7z-compressed | +| gif | image/gif | atom | application/atom+xml | +| png | image/png | pdf | application/pdf | +| svg | image/svg+xml | mjs, js | application/javascript | +| webp | image/webp | json | application/json | +| ico | image/x-icon | rss | application/rss+xml | +| tif | image/tiff | tar | application/x-tar | +| tiff | image/tiff | xhtml, xht | application/xhtml+xml | +| jpeg, jpg | image/jpeg | xslt | application/xslt+xml | +| mp4 | video/mp4 | xml | application/xml | +| mpeg | video/mpeg | gz | application/gzip | +| webm | video/webm | zip | application/zip | +| mp3 | audio/mp3 | wasm | application/wasm | + +### File request handler + +```cpp +// The handler is called right before the response is sent to a client +svr.set_file_request_handler([](const Request &req, Response &res) { + ... +}); +``` + +NOTE: These static file server methods are not thread-safe. + +### Logging + +```cpp +svr.set_logger([](const auto& req, const auto& res) { + your_logger(req, res); +}); +``` + +### Error handler + +```cpp +svr.set_error_handler([](const auto& req, auto& res) { + auto fmt = "

Error Status: %d

"; + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), fmt, res.status); + res.set_content(buf, "text/html"); +}); +``` + +### Exception handler +The exception handler gets called if a user routing handler throws an error. + +```cpp +svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { + auto fmt = "

Error 500

%s

"; + char buf[BUFSIZ]; + try { + std::rethrow_exception(ep); + } catch (std::exception &e) { + snprintf(buf, sizeof(buf), fmt, e.what()); + } catch (...) { // See the following NOTE + snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); + } + res.set_content(buf, "text/html"); + res.status = 500; +}); +``` + +NOTE: if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful! + +### Pre routing handler + +```cpp +svr.set_pre_routing_handler([](const auto& req, auto& res) { + if (req.path == "/hello") { + res.set_content("world", "text/html"); + return Server::HandlerResponse::Handled; + } + return Server::HandlerResponse::Unhandled; +}); +``` + +### Post routing handler + +```cpp +svr.set_post_routing_handler([](const auto& req, auto& res) { + res.set_header("ADDITIONAL_HEADER", "value"); +}); +``` + +### 'multipart/form-data' POST data + +```cpp +svr.Post("/multipart", [&](const auto& req, auto& res) { + auto size = req.files.size(); + auto ret = req.has_file("name1"); + const auto& file = req.get_file_value("name1"); + // file.filename; + // file.content_type; + // file.content; +}); +``` + +### Receive content with a content receiver + +```cpp +svr.Post("/content_receiver", + [&](const Request &req, Response &res, const ContentReader &content_reader) { + if (req.is_multipart_form_data()) { + // NOTE: `content_reader` is blocking until every form data field is read + MultipartFormDataItems files; + content_reader( + [&](const MultipartFormData &file) { + files.push_back(file); + return true; + }, + [&](const char *data, size_t data_length) { + files.back().content.append(data, data_length); + return true; + }); + } else { + std::string body; + content_reader([&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); + } + }); +``` + +### Send content with the content provider + +```cpp +const size_t DATA_CHUNK_SIZE = 4; + +svr.Get("/stream", [&](const Request &req, Response &res) { + auto data = new std::string("abcdefg"); + + res.set_content_provider( + data->size(), // Content length + "text/plain", // Content type + [data](size_t offset, size_t length, DataSink &sink) { + const auto &d = *data; + sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE)); + return true; // return 'false' if you want to cancel the process. + }, + [data](bool success) { delete data; }); +}); +``` + +Without content length: + +```cpp +svr.Get("/stream", [&](const Request &req, Response &res) { + res.set_content_provider( + "text/plain", // Content type + [&](size_t offset, DataSink &sink) { + if (/* there is still data */) { + std::vector data; + // prepare data... + sink.write(data.data(), data.size()); + } else { + sink.done(); // No more data + } + return true; // return 'false' if you want to cancel the process. + }); +}); +``` + +### Chunked transfer encoding + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done(); // No more data + return true; // return 'false' if you want to cancel the process. + } + ); +}); +``` + +With trailer: + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_header("Trailer", "Dummy1, Dummy2"); + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done_with_trailer({ + {"Dummy1", "DummyVal1"}, + {"Dummy2", "DummyVal2"} + }); + return true; + } + ); +}); +``` + +### 'Expect: 100-continue' handler + +By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header. + +```cpp +// Send a '417 Expectation Failed' response. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return 417; +}); +``` + +```cpp +// Send a final status without reading the message body. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return res.status = 401; +}); +``` + +### Keep-Alive connection + +```cpp +svr.set_keep_alive_max_count(2); // Default is 5 +svr.set_keep_alive_timeout(10); // Default is 5 +``` + +### Timeout + +```c++ +svr.set_read_timeout(5, 0); // 5 seconds +svr.set_write_timeout(5, 0); // 5 seconds +svr.set_idle_interval(0, 100000); // 100 milliseconds +``` + +### Set maximum payload length for reading a request body + +```c++ +svr.set_payload_max_length(1024 * 1024 * 512); // 512MB +``` + +### Server-Sent Events + +Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc). + +### Default thread pool support + +`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. + +If you want to set the thread count at runtime, there is no convenient way... But here is how. + +```cpp +svr.new_task_queue = [] { return new ThreadPool(12); }; +``` + +### Override the default thread pool with yours + +You can supply your own thread pool implementation according to your need. + +```cpp +class YourThreadPoolTaskQueue : public TaskQueue { +public: + YourThreadPoolTaskQueue(size_t n) { + pool_.start_with_thread_count(n); + } + + virtual void enqueue(std::function fn) override { + pool_.enqueue(fn); + } + + virtual void shutdown() override { + pool_.shutdown_gracefully(); + } + +private: + YourThreadPool pool_; +}; + +svr.new_task_queue = [] { + return new YourThreadPoolTaskQueue(12); +}; +``` + +Client +------ + +```c++ +#include +#include + +int main(void) +{ + httplib::Client cli("localhost", 1234); + + if (auto res = cli.Get("/hi")) { + if (res->status == 200) { + std::cout << res->body << std::endl; + } + } else { + auto err = res.error(); + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } +} +``` + +NOTE: Constructor with scheme-host-port string is now supported! + +```c++ +httplib::Client cli("localhost"); +httplib::Client cli("localhost:8080"); +httplib::Client cli("http://localhost"); +httplib::Client cli("http://localhost:8080"); +httplib::Client cli("https://localhost"); +httplib::SSLClient cli("localhost"); +``` + +### Error code + +Here is the list of errors from `Result::error()`. + +```c++ +enum Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, +}; +``` + +### GET with HTTP headers + +```c++ +httplib::Headers headers = { + { "Accept-Encoding", "gzip, deflate" } +}; +auto res = cli.Get("/hi", headers); +``` +or +```c++ +auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}}); +``` +or +```c++ +cli.set_default_headers({ + { "Accept-Encoding", "gzip, deflate" } +}); +auto res = cli.Get("/hi"); +``` + +### POST + +```c++ +res = cli.Post("/post", "text", "text/plain"); +res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded"); +``` + +### POST with parameters + +```c++ +httplib::Params params; +params.emplace("name", "john"); +params.emplace("note", "coder"); + +auto res = cli.Post("/post", params); +``` + or + +```c++ +httplib::Params params{ + { "name", "john" }, + { "note", "coder" } +}; + +auto res = cli.Post("/post", params); +``` + +### POST with Multipart Form Data + +```c++ +httplib::MultipartFormDataItems items = { + { "text1", "text default", "", "" }, + { "text2", "aωb", "", "" }, + { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, + { "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" }, + { "file3", "", "", "application/octet-stream" }, +}; + +auto res = cli.Post("/multipart", items); +``` + +### PUT + +```c++ +res = cli.Put("/resource/foo", "text", "text/plain"); +``` + +### DELETE + +```c++ +res = cli.Delete("/resource/foo"); +``` + +### OPTIONS + +```c++ +res = cli.Options("*"); +res = cli.Options("/resource/foo"); +``` + +### Timeout + +```c++ +cli.set_connection_timeout(0, 300000); // 300 milliseconds +cli.set_read_timeout(5, 0); // 5 seconds +cli.set_write_timeout(5, 0); // 5 seconds +``` + +### Receive content with a content receiver + +```c++ +std::string body; + +auto res = cli.Get("/large-data", + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); +``` + +```cpp +std::string body; + +auto res = cli.Get( + "/stream", Headers(), + [&](const Response &response) { + EXPECT_EQ(200, response.status); + return true; // return 'false' if you want to cancel the request. + }, + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; // return 'false' if you want to cancel the request. + }); +``` + +### Send content with a content provider + +```cpp +std::string body = ...; + +auto res = cli.Post( + "/stream", body.size(), + [](size_t offset, size_t length, DataSink &sink) { + sink.write(body.data() + offset, length); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### Chunked transfer encoding + +```cpp +auto res = cli.Post( + "/stream", + [](size_t offset, DataSink &sink) { + sink.os << "chunked data 1"; + sink.os << "chunked data 2"; + sink.os << "chunked data 3"; + sink.done(); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### With Progress Callback + +```cpp +httplib::Client client(url, port); + +// prints: 0 / 000 bytes => 50% complete +auto res = cli.Get("/", [](uint64_t len, uint64_t total) { + printf("%lld / %lld bytes => %d%% complete\n", + len, total, + (int)(len*100/total)); + return true; // return 'false' if you want to cancel the request. +} +); +``` + +![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) + +### Authentication + +```cpp +// Basic Authentication +cli.set_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_bearer_token_auth("token"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Proxy server support + +```cpp +cli.set_proxy("host", port); + +// Basic Authentication +cli.set_proxy_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_proxy_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_proxy_bearer_token_auth("pass"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Range + +```cpp +httplib::Client cli("httpbin.org"); + +auto res = cli.Get("/range/32", { + httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10' +}); +// res->status should be 206. +// res->body should be "bcdefghijk". +``` + +```cpp +httplib::make_range_header({{1, 10}, {20, -1}}) // 'Range: bytes=1-10, 20-' +httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599' +httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1' +``` + +### Keep-Alive connection + +```cpp +httplib::Client cli("localhost", 1234); + +cli.Get("/hello"); // with "Connection: close" + +cli.set_keep_alive(true); +cli.Get("/world"); + +cli.set_keep_alive(false); +cli.Get("/last-request"); // with "Connection: close" +``` + +### Redirect + +```cpp +httplib::Client cli("yahoo.com"); + +auto res = cli.Get("/"); +res->status; // 301 + +cli.set_follow_location(true); +res = cli.Get("/"); +res->status; // 200 +``` + +### Use a specific network interface + +NOTE: This feature is not available on Windows, yet. + +```cpp +cli.set_interface("eth0"); // Interface name, IP address or host name +``` + +Compression +----------- + +The server can apply compression to the following MIME type contents: + + * all text types except text/event-stream + * image/svg+xml + * application/javascript + * application/json + * application/xml + * application/xhtml+xml + +### Zlib Support + +'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked. + +### Brotli Support + +Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/google/brotli for more detail. + +### Compress request body on client + +```c++ +cli.set_compress(true); +res = cli.Post("/resource/foo", "...", "text/plain"); +``` + +### Compress response body on client + +```c++ +cli.set_decompress(false); +res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); +res->body; // Compressed data +``` + +Use `poll` instead of `select` +------------------------------ + +`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`. + + +Split httplib.h into .h and .cc +------------------------------- + +```console +$ ./split.py -h +usage: split.py [-h] [-e EXTENSION] [-o OUT] + +This script splits httplib.h into .h and .cc parts. + +optional arguments: + -h, --help show this help message and exit + -e EXTENSION, --extension EXTENSION + extension of the implementation file (default: cc) + -o OUT, --out OUT where to write the files (default: out) + +$ ./split.py +Wrote out/httplib.h and out/httplib.cc +``` + +NOTE +---- + +### g++ + +g++ 4.8 and below cannot build this library since `` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). + +### Windows + +Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand. + +```cpp +#include +#include +``` + +```cpp +#define WIN32_LEAN_AND_MEAN +#include +#include +``` + +NOTE: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. + +NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported. + +License +------- + +MIT license (© 2023 Yuji Hirose) + +Special Thanks To +----------------- + +[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy! diff --git a/DeviceActivate/NetraLib/include/encrypt.hpp b/DeviceActivate/NetraLib/include/encrypt.hpp new file mode 100644 index 0000000..42ea793 --- /dev/null +++ b/DeviceActivate/NetraLib/include/encrypt.hpp @@ -0,0 +1,14 @@ +#pragma once + +/* +主要是用于各种加密 +*/ + +#include "QCL_Include.hpp" + +using namespace std; + +namespace encrypt +{ + string MD5(const string &info); +} \ No newline at end of file diff --git a/DeviceActivate/NetraLib/include/httplib.h b/DeviceActivate/NetraLib/include/httplib.h new file mode 100644 index 0000000..1bdef69 --- /dev/null +++ b/DeviceActivate/NetraLib/include/httplib.h @@ -0,0 +1,8794 @@ +// +// httplib.h +// +// Copyright (c) 2023 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.12.2" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif // strcasecmp + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#ifndef _AIX +#include +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x1010100fL +#error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +struct ci { + bool operator()(const std::string &s1, const std::string &s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); + } +}; + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +} // namespace detail + +using Headers = std::multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + Params params; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + void set_redirect(const std::string &url, int status = 302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + template + ssize_t write_format(const char *fmt, const Args &...args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual void enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool : public TaskQueue { +public: + explicit ThreadPool(size_t n) : shutdown_(false) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + void enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = std::move(pool_.jobs_.front()); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_file_request_handler(Handler handler); + + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = std::vector>; + using HandlersForContentReader = + std::vector>; + + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, const Handlers &handlers); + bool + dispatch_request_for_content_reader(Request &req, Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers); + + bool parse_request_line(const char *s, Request &req); + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); + bool write_response(Stream &strm, bool close_connection, const Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + + virtual bool process_and_close_socket(socket_t sock); + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + + std::atomic is_running_{false}; + std::atomic done_{false}; + std::map file_extension_and_mimetype_map_; + Handler file_request_handler_; + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Logger logger_; + Expect100ContinueHandler expect_100_continue_handler_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(const Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; + template + T get_request_header_value(const std::string &key, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_; + Headers request_headers_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); + + void copy_settings(const ClientImpl &rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif + + Logger logger_; + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + Client(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +template +inline T get_header_value(const Headers & /*headers*/, + const std::string & /*key*/, size_t /*id*/ = 0, + uint64_t /*def*/ = 0) {} + +template <> +inline uint64_t get_header_value(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +template +inline T Request::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline T Response::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf{}; + + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +template +inline T Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, 0); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(Ranges ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void split(const char *b, const char *e, char d, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + virtual ~nocompressor() = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor(); + ~gzip_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + int v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + const char *charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = (code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + int val = 0; + int valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_file(const std::string &path) { +#ifdef _WIN32 + return _access_s(path.c_str(), 0) == 0; +#else + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +#endif +} + +inline bool is_dir(const std::string &path) { + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + int val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + int val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + size_t i = 0; + size_t beg = 0; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { break; } + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } +} + +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = false; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024 * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + +inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; + } + } +} + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET; + + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + std::copy(host.begin(), host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + + fcntl(sock, F_SETFD, FD_CLOEXEC); + if (socket_options) { socket_options(sock); } + + if (!bind_or_connect(sock, hints)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + if (sock == INVALID_SOCKET) { continue; } + +#ifndef _WIN32 + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + int yes = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), + sizeof(yes)); + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + int no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), + sizeof(no)); + } + + // bind or connect + if (bind_or_connect(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + freeaddrinfo(result); + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + freeifaddrs(ifap); + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + freeifaddrs(ifap); + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + freeifaddrs(ifap); + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if.c_str())) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { return false; } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline const char * +find_content_type(const std::string &path, + const std::map &user_data) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second.c_str(); } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return nullptr; + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline const char *status_message(int status) { + switch (status) { + case 100: return "Continue"; + case 101: return "Switching Protocol"; + case 102: return "Processing"; + case 103: return "Early Hints"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choice"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "unused"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Too Early"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + + default: + case 500: return "Internal Server Error"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + default: + return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + int ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + int ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + auto prev_avail_in = strm_.avail_in; + + ret = inflate(&strm_, Z_NO_FLUSH); + + if (prev_avail_in - strm_.avail_in == 0) { return false; } + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) return false; + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +inline bool compare_case_ignore(const std::string &a, const std::string &b) { + if (a.size() != b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + auto key = std::string(beg, key_end); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + fn(std::move(key), std::move(val)); + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + } else { + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; + } +#else + } else { + continue; // Skip invalid line. + } +#endif + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n < 0) { + return false; + } else if (n == 0) { + return true; + } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n")) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n")) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), + "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = 500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value(x.headers, "Content-Length"); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { status = exceed_payload_max_length ? 413 : 400; } + return ret; + }); +} // namespace detail + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + res.location = location; + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = content_type.substr(beg, end - beg); + if (boundary.length() >= 2 && boundary.front() == '"' && + boundary.back() == '"') { + boundary = boundary.substr(1, boundary.size() - 2); + } + return !boundary.empty(); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); + std::smatch m; + if (std::regex_match(s, m, re_first_range)) { + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); + bool all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) return; + static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); + std::cmatch cm; + if (std::regex_match(b, e, cm, re_another_range)) { + ssize_t first = -1; + if (!cm.str(1).empty()) { + first = static_cast(std::stoll(cm.str(1))); + } + + ssize_t last = -1; + if (!cm.str(2).empty()) { + last = static_cast(std::stoll(cm.str(2))); + } + + if (first != -1 && last != -1 && first > last) { + all_valid_ranges = false; + return; + } + ranges.emplace_back(std::make_pair(first, last)); + } + }); + return all_valid_ranges; + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + // TODO: support 'filename*' + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~", + std::regex_constants::icase); + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + static const std::string header_name = "content-type:"; + const auto header = buf_head(pos); + if (start_with_case_ignore(header, header_name)) { + file_.content_type = trim_copy(header.substr(header_name.size())); + } else { + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + file_.name = m[1]; + file_.filename = m[2]; + } else { + is_valid_ = false; + return false; + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_crlf_.size() > buf_size()) { return true; } + if (buf_start_with(dash_crlf_)) { + buf_erase(dash_crlf_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + const std::string dash_crlf_ = "--\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string to_lower(const char *beg, const char *end) { + std::string out; + auto it = beg; + while (it != end) { + out += static_cast(::tolower(*it)); + it++; + } + return out; +} + +inline std::string make_multipart_data_boundary() { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); + + std::string result = "--cpp-httplib-multipart-data-"; + + for (auto i = 0; i < 16; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + + return result; +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) body += serialize_multipart_formdata_finish(boundary); + + return body; +} + +inline std::pair +get_range_offset_and_length(const Request &req, size_t content_length, + size_t index) { + auto r = req.ranges[index]; + + if (r.first == -1 && r.second == -1) { + return std::make_pair(0, content_length); + } + + auto slen = static_cast(content_length); + + if (r.first == -1) { + r.first = (std::max)(static_cast(0), slen - r.second); + r.second = slen - 1; + } + + if (r.second == -1) { r.second = slen - 1; } + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field(size_t offset, size_t length, + size_t content_length) { + std::string field = "bytes "; + field += std::to_string(offset); + field += "-"; + field += std::to_string(offset + length - 1); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + SToken stoken, CToken ctoken, + Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offsets = get_range_offset_and_length(req, res.body.size(), i); + auto offset = offsets.first; + auto length = offsets.second; + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset, length, res.body.size())); + ctoken("\r\n"); + ctoken("\r\n"); + if (!content(offset, length)) { return false; } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--\r\n"); + + return true; +} + +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; + }); +} + +inline size_t +get_multipart_ranges_data_length(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool write_multipart_ranges_data(Stream &strm, const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + const T &is_shutting_down) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline std::pair +get_range_offset_and_length(const Request &req, const Response &res, + size_t index) { + auto r = req.ranges[index]; + + if (r.second == -1) { + r.second = static_cast(res.content_length_) - 1; + } + + return std::make_pair(r.first, r.second - r.first + 1); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << (unsigned int)hash[i]; + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (int i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + auto m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 +inline std::string random_string(size_t length) { + auto randchar = []() -> char { + const char charset[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[static_cast(std::rand()) % max_index]; + }; + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + int dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } + + freeaddrinfo(result); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair make_range_header(Ranges ranges) { + std::string field = "bytes="; + auto i = 0; + for (auto r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const std::string &key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = 302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = true; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() {} + +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() {} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + if (detail::is_dir(dir)) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(HandlerWithResponse handler) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + if (bind_internal(host, port, socket_flags) < 0) return false; + return true; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + return bind_internal(host, 0, socket_flags); +} + +inline bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + auto se = detail::scope_exit([&]() { done_ = true; }); + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } +} + +inline bool Server::parse_request_line(const char *s, Request &req) { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + size_t count = 0; + + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), req.params); + } + break; + } + default: break; + } + count++; + }); + + if (count > 2) { return false; } + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); + } + + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + res.set_header("Content-Type", "text/plain"); + } + + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { + return false; + } + + if (!detail::write_headers(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + res.content_provider_success_ = false; + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = 413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); + if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } + res.status = req.has_header("Range") ? 206 : 200; + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + task_queue->enqueue([this, sock]() { process_and_close_socket(sock); }); + } + + task_queue->shutdown(); + } + + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + bool is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = 400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { + if (req.ranges.size() > 1) { + boundary = detail::make_multipart_data_boundary(); + + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + res.headers.emplace("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty()) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length(req, res, boundary, + content_type); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty()) { + ; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.body.size(), 0); + auto offset = offsets.first; + auto length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.body.size()); + res.set_header("Content-Range", content_range); + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } + } else { + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + Response res; + + res.version = "HTTP/1.1"; + + for (const auto &header : default_headers_) { + if (res.headers.find(header.first) == res.headers.end()) { + res.headers.insert(header); + } + } + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = 400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = 416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + auto status = 100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case 100: + case 417: + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + detail::status_message(status)); + break; + default: return write_response(strm, close_connection, req, res); + } + } + + // Rounting + bool routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + + if (routed) { + if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(host), port_(port), + host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) ip = it->second; + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == 100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == 407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.headers.emplace("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.headers.emplace("User-Agent", agent); + } +#endif + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.headers.emplace("Content-Length", length); + } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.headers.emplace("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.headers.emplace("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.headers.emplace("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + detail::write_headers(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.headers.emplace("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + ; + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by + // handle_request which is only called by send, which locks the request + // mutex during the process. It would be a bug to call it from a different + // thread since it's a thread-safety issue to do these things to the socket + // if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + size_t cur_item = 0, cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && items.size()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + bool has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + return false; + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, response_handler, + content_receiver, progress); +} + +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + int res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() {} + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + // add default password callback before opening encrypted private key + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata(ctx_, + (char *)private_key_password); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response res2; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, res2, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (res2.status == 407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res2, auth, true)) { + Response res3; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, res3, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } else { + res = res2; + return false; + } + } + + return true; +} + +inline bool SSLClient::load_certs() { + bool ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl2) { + SSL_set_tlsext_host_name(ssl2, host_.c_str()); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6; + struct in_addr addr; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); + auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(std::string(b, e)); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() {} + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); +} + +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline socket_t Client::socket() const { return cli_->socket(); } + +inline void Client::stop() { cli_->stop(); } + +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + +inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/DeviceActivate/NetraLib/src/NetRequest.cpp b/DeviceActivate/NetraLib/src/NetRequest.cpp new file mode 100644 index 0000000..f3aa541 --- /dev/null +++ b/DeviceActivate/NetraLib/src/NetRequest.cpp @@ -0,0 +1,635 @@ +#include "NetRequest.hpp" + +#include +#include +#include +#include +#include +#include + +namespace ntq +{ + namespace + { + static std::string joinPath(const std::string &base, const std::string &path) + { + if (base.empty()) return path.empty() || path[0] == '/' ? path : std::string("/") + path; + if (path.empty()) return base[0] == '/' ? base : std::string("/") + base; + bool base_has = base.front() == '/'; + bool base_end = base.back() == '/'; + bool path_has = path.front() == '/'; + std::string b = base_has ? base : std::string("/") + base; + if (base_end && path_has) return b + path.substr(1); + if (!base_end && !path_has) return b + "/" + path; + return b + path; + } + + static std::string paramsToQuery(const httplib::Params ¶ms) + { + if (params.empty()) return {}; + std::string s; + bool first = true; + for (auto &kv : params) + { + if (!first) s += '&'; + first = false; + s += kv.first; + s += '='; + s += kv.second; + } + return s; + } + + static httplib::Headers mergeHeaders(const httplib::Headers &a, const httplib::Headers &b) + { + httplib::Headers h = a; + for (auto &kv : b) + { + // 覆盖同名 header:先删再插 + h.erase(kv.first); + h.emplace(kv.first, kv.second); + } + return h; + } + } + + class ConcurrencyGate + { + public: + explicit ConcurrencyGate(size_t limit) : limit_(limit), active_(0) {} + + void set_limit(size_t limit) + { + std::lock_guard lk(mtx_); + limit_ = limit > 0 ? limit : 1; + cv_.notify_all(); + } + + struct Guard + { + ConcurrencyGate &g; + explicit Guard(ConcurrencyGate &gate) : g(gate) { g.enter(); } + ~Guard() { g.leave(); } + }; + + private: + friend struct Guard; + void enter() + { + std::unique_lock lk(mtx_); + cv_.wait(lk, [&]{ return active_ < limit_; }); + ++active_; + } + void leave() + { + std::lock_guard lk(mtx_); + if (active_ > 0) --active_; + cv_.notify_one(); + } + + size_t limit_; + size_t active_; + std::mutex mtx_; + std::condition_variable cv_; + }; + + struct NetRequest::Impl + { + RequestOptions opts; + LogCallback logger; + Stats stats; + + // 并发控制 + ConcurrencyGate gate{4}; + + // 缓存 + struct CacheEntry + { + HttpResponse resp; + std::chrono::steady_clock::time_point expiry; + }; + bool cache_enabled = false; + std::chrono::milliseconds cache_ttl{0}; + std::unordered_map cache; + std::mutex cache_mtx; + + void log(const std::string &msg) + { + if (logger) logger(msg); + } + + template + void apply_client_options(ClientT &cli) + { + const time_t c_sec = static_cast(opts.connect_timeout_ms / 1000); + const time_t c_usec = static_cast((opts.connect_timeout_ms % 1000) * 1000); + const time_t r_sec = static_cast(opts.read_timeout_ms / 1000); + const time_t r_usec = static_cast((opts.read_timeout_ms % 1000) * 1000); + const time_t w_sec = static_cast(opts.write_timeout_ms / 1000); + const time_t w_usec = static_cast((opts.write_timeout_ms % 1000) * 1000); + cli.set_connection_timeout(c_sec, c_usec); + cli.set_read_timeout(r_sec, r_usec); + cli.set_write_timeout(w_sec, w_usec); + cli.set_keep_alive(opts.keep_alive); + } + + std::string build_full_path(const std::string &path) const + { + return joinPath(opts.base_path, path); + } + + std::string cache_key(const std::string &path, const httplib::Params ¶ms, const httplib::Headers &headers) + { + std::ostringstream oss; + oss << opts.scheme << "://" << opts.host << ':' << opts.port << build_full_path(path); + if (!params.empty()) oss << '?' << paramsToQuery(params); + for (auto &kv : headers) oss << '|' << kv.first << '=' << kv.second; + return oss.str(); + } + + void record_latency(double ms) + { + stats.last_latency_ms = ms; + const double alpha = 0.2; + if (stats.avg_latency_ms <= 0.0) stats.avg_latency_ms = ms; + else stats.avg_latency_ms = alpha * ms + (1.0 - alpha) * stats.avg_latency_ms; + } + + static ErrorCode map_error() + { + // 简化:无法区分具体错误码,统一归为 Network + return ErrorCode::Network; + } + }; + + NetRequest::NetRequest(const RequestOptions &options) + : impl_(new Impl) + { + impl_->opts = options; + if (impl_->opts.scheme == "https" && impl_->opts.port == 80) impl_->opts.port = 443; + if (impl_->opts.scheme == "http" && impl_->opts.port == 0) impl_->opts.port = 80; + } + + NetRequest::~NetRequest() + { + delete impl_; + } + + void NetRequest::setLogger(LogCallback logger) + { + impl_->logger = std::move(logger); + } + + void NetRequest::setMaxConcurrentRequests(size_t n) + { + impl_->gate.set_limit(n > 0 ? n : 1); + } + + void NetRequest::enableCache(std::chrono::milliseconds ttl) + { + impl_->cache_enabled = true; + impl_->cache_ttl = ttl.count() > 0 ? ttl : std::chrono::milliseconds(1000); + } + + void NetRequest::disableCache() + { + impl_->cache_enabled = false; + std::lock_guard lk(impl_->cache_mtx); + impl_->cache.clear(); + } + + ntq::optional NetRequest::Get(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, mergeHeaders(impl_->opts.default_headers, headers)); + std::lock_guard lk(impl_->cache_mtx); + auto it = impl_->cache.find(key); + if (it != impl_->cache.end() && std::chrono::steady_clock::now() < it->second.expiry) + { + if (err) *err = ErrorCode::None; + auto resp = it->second.resp; + resp.from_cache = true; + return resp; + } + } + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + impl_->log("HTTPS requested but OpenSSL is not enabled; falling back to error."); + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!result.has_value()) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, merged_headers); + std::lock_guard lk(impl_->cache_mtx); + impl_->cache[key] = Impl::CacheEntry{*result, std::chrono::steady_clock::now() + impl_->cache_ttl}; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + std::future> NetRequest::GetAsync(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, query, headers, err]() mutable { + ErrorCode local; + auto r = Get(path, query, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, json, headers, err]() mutable { + ErrorCode local; + auto r = PostJson(path, json, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, form, headers, err]() mutable { + ErrorCode local; + auto r = PostForm(path, form, headers, &local); + if (err) *err = local; + return r; + }); + } + + bool NetRequest::DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers, + bool resume, + size_t /*chunk_size*/, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + std::ios_base::openmode mode = std::ios::binary | std::ios::out; + size_t offset = 0; + if (resume) + { + std::ifstream in(local_file, std::ios::binary | std::ios::ate); + if (in) + { + offset = static_cast(in.tellg()); + } + mode |= std::ios::app; + } + else + { + mode |= std::ios::trunc; + } + + std::ofstream out(local_file, mode); + if (!out) + { + if (err) *err = ErrorCode::IOError; + impl_->stats.total_errors++; + return false; + } + + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + if (resume && offset > 0) + { + merged_headers.emplace("Range", "bytes=" + std::to_string(offset) + "-"); + } + + const auto full_path = impl_->build_full_path(path); + int status_code = 0; + ErrorCode local_err = ErrorCode::None; + + auto content_receiver = [&](const char *data, size_t data_length) { + out.write(data, static_cast(data_length)); + return static_cast(out); + }; + + bool ok = false; + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } + } + + out.close(); + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!ok) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return false; + } + if (err) *err = ErrorCode::None; + return true; + } + + NetRequest::Stats NetRequest::getStats() const + { + return impl_->stats; + } + + // ------------------------- Quick helpers ------------------------- + namespace { + struct ParsedURL { + std::string scheme; + std::string host; + int port = 0; + std::string path_and_query; + bool ok = false; + }; + + static ParsedURL parse_url(const std::string &url) + { + ParsedURL p; p.ok = false; + // very small parser: scheme://host[:port]/path[?query] + auto pos_scheme = url.find("://"); + if (pos_scheme == std::string::npos) return p; + p.scheme = url.substr(0, pos_scheme); + size_t pos_host = pos_scheme + 3; + + size_t pos_path = url.find('/', pos_host); + std::string hostport = pos_path == std::string::npos ? url.substr(pos_host) + : url.substr(pos_host, pos_path - pos_host); + auto pos_colon = hostport.find(':'); + if (pos_colon == std::string::npos) { + p.host = hostport; + p.port = (p.scheme == "https") ? 443 : 80; + } else { + p.host = hostport.substr(0, pos_colon); + std::string port_str = hostport.substr(pos_colon + 1); + p.port = port_str.empty() ? ((p.scheme == "https") ? 443 : 80) : std::atoi(port_str.c_str()); + } + p.path_and_query = (pos_path == std::string::npos) ? "/" : url.substr(pos_path); + p.ok = !p.host.empty(); + return p; + } + } + + ntq::optional NetRequest::QuickGet(const std::string &url, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.Get(p.path_and_query, {}, headers, err); + } + + ntq::optional NetRequest::QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostJson(p.path_and_query, json, headers, err); + } + + ntq::optional NetRequest::QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostForm(p.path_and_query, form, headers, err); + } +} diff --git a/DeviceActivate/NetraLib/src/Netra.cpp b/DeviceActivate/NetraLib/src/Netra.cpp new file mode 100644 index 0000000..61b9beb --- /dev/null +++ b/DeviceActivate/NetraLib/src/Netra.cpp @@ -0,0 +1,693 @@ +#include "Netra.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TcpServer::TcpServer(int port) + : port_(port), running_(false), serverSock_(-1) {} + + /** + * @brief 析构函数中调用stop()确保服务器资源被释放 + */ + TcpServer::~TcpServer() + { + stop(); + } + + /** + * @brief 启动服务器: + * 1. 创建监听socket(TCP) + * 2. 绑定端口 + * 3. 监听端口 + * 4. 启动监听线程acceptThread_ + * + * @return 成功返回true,失败返回false + */ + bool TcpServer::start() + { + // 创建socket + serverSock_ = socket(AF_INET, SOCK_STREAM, 0); + if (serverSock_ < 0) + { + std::cerr << "Socket 创建失败\n"; + return false; + } + + // 设置socket地址结构 + sockaddr_in serverAddr; + std::memset(&serverAddr, 0, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port_); // 端口转网络字节序 + serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡IP + + // 允许端口重用,防止服务器异常关闭后端口被占用 + int opt = 1; + setsockopt(serverSock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + // 绑定端口 + if (bind(serverSock_, (sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) + { + std::cerr << "绑定失败\n"; + return false; + } + + // 开始监听,最大等待连接数为5 + if (listen(serverSock_, 5) < 0) + { + std::cerr << "监听失败\n"; + return false; + } + + // 设置运行标志为true + running_ = true; + + // 启动专门接受客户端连接的线程 + acceptThread_ = std::thread(&TcpServer::acceptClients, this); + + std::cout << "服务器启动,监听端口:" << port_ << std::endl; + return true; + } + + /** + * @brief 停止服务器: + * 1. 设置运行标志为false,通知线程退出 + * 2. 关闭监听socket + * 3. 关闭所有客户端socket,清理客户端列表 + * 4. 等待所有线程退出 + */ + void TcpServer::stop() + { + running_ = false; + + if (serverSock_ >= 0) + { + close(serverSock_); + serverSock_ = -1; + } + + { + // 线程安全关闭所有客户端socket + std::lock_guard lock(clientsMutex_); + for (int sock : clientSockets_) + { + close(sock); + } + clientSockets_.clear(); + } + + // 等待监听线程退出 + if (acceptThread_.joinable()) + acceptThread_.join(); + + // 等待所有客户端处理线程退出 + for (auto &t : clientThreads_) + { + if (t.joinable()) + t.join(); + } + + std::cout << "服务器已停止\n"; + } + + /** + * @brief acceptClients函数循环监听客户端连接请求 + * 每当accept成功: + * 1. 打印客户端IP和Socket信息 + * 2. 线程安全地将客户端Socket加入clientSockets_列表 + * 3. 创建新线程调用handleClient处理该客户端收发 + */ + void TcpServer::acceptClients() + { + while (running_) + { + sockaddr_in clientAddr; + socklen_t clientLen = sizeof(clientAddr); + int clientSock = accept(serverSock_, (sockaddr *)&clientAddr, &clientLen); + if (clientSock < 0) + { + if (running_) + std::cerr << "接受连接失败\n"; + continue; + } + + // 将客户端IP转换成字符串格式打印 + char clientIP[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(clientAddr.sin_addr), clientIP, INET_ADDRSTRLEN); + std::cout << "客户端连接,IP: " << clientIP << ", Socket: " << clientSock << std::endl; + + { + // 加锁保护共享的clientSockets_容器 + std::lock_guard lock(clientsMutex_); + clientSockets_.push_back(clientSock); + } + } + } + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端socket + * @param message 发送消息内容 + */ + void TcpServer::sendToClient(int clientSock, const std::string &message) + { + send(clientSock, message.c_str(), message.size(), 0); + } + + /** + * @brief 单次接收指定客户端数据 + * @param clientSock 客户端socket + */ + std::string TcpServer::receiveFromClient(int clientSock, bool flag) + { + char buffer[1024]; + std::memset(buffer, 0, sizeof(buffer)); + + int flags = flag ? 0 : MSG_DONTWAIT; + ssize_t bytesReceived = recv(clientSock, buffer, sizeof(buffer) - 1, flags); + + if (bytesReceived <= 0) + return {}; + + return std::string(buffer, bytesReceived); + } + + /** + * @brief 获取当前所有客户端Socket副本(线程安全) + * @return 包含所有客户端socket的vector副本 + */ + std::vector TcpServer::getClientSockets() + { + std::lock_guard lock(clientsMutex_); + return clientSockets_; + } + + void TcpServer::removeClient(int clientSock) + { + std::lock_guard lock(clientsMutex_); + for (auto it = clientSockets_.begin(); it != clientSockets_.end(); ++it) + { + if (*it == clientSock) + { + close(*it); + clientSockets_.erase(it); + break; + } + } + } + + bool TcpServer::isClientDisconnected(int clientSock) + { + char tmp; + ssize_t n = recv(clientSock, &tmp, 1, MSG_PEEK | MSG_DONTWAIT); + if (n == 0) + return true; // 对端有序关闭 + if (n < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return false; // 只是暂时无数据 + return true; // 其它错误视为断开 + } + return false; // 有数据可读 + } + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *TcpServer::getClientIPAndPort(int clientSock) + { + struct sockaddr_in addr; + socklen_t addr_size = sizeof(addr); + + // 获取客户端地址信息 + if (getpeername(clientSock, (struct sockaddr *)&addr, &addr_size) == -1) + { + perror("getpeername failed"); + return NULL; + } + + // 分配内存存储结果(格式: "IP:PORT") + char *result = (char *)malloc(INET_ADDRSTRLEN + 10); + if (!result) + return NULL; + + // 转换IP和端口 + char *ip = inet_ntoa(addr.sin_addr); + unsigned short port = ntohs(addr.sin_port); + + snprintf(result, INET_ADDRSTRLEN + 10, "%s:%d", ip, port); + return result; + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + WriteFile::WriteFile(const std::string &filePath) + : filePath_(filePath) {} + + /** + * @brief 覆盖写文本(线程安全) + */ + bool WriteFile::overwriteText(const std::string &content) + { + std::lock_guard lock(writeMutex_); // 加锁 + return writeToFile(content, std::ios::out | std::ios::trunc); + } + + /** + * @brief 追加写文本(线程安全) + */ + bool WriteFile::appendText(const std::string &content) + { + std::lock_guard lock(writeMutex_); + return writeToFile(content, std::ios::out | std::ios::app); + } + + /** + * @brief 覆盖写二进制(线程安全) + */ + bool WriteFile::overwriteBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::trunc | std::ios::binary); + } + + /** + * @brief 追加写二进制(线程安全) + */ + bool WriteFile::appendBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::app | std::ios::binary); + } + + /** + * @brief 通用文本写入(私有) + */ + bool WriteFile::writeToFile(const std::string &content, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file << content; + file.close(); + return true; + } + + /** + * @brief 通用二进制写入(私有) + */ + bool WriteFile::writeBinary(const std::vector &data, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file.write(data.data(), data.size()); + file.close(); + return true; + } + + size_t WriteFile::countBytesPattern(const std::string &pattern, bool includePattern) + { + std::lock_guard lock(writeMutex_); + + if (pattern.empty()) + return 0; + + std::ifstream file(filePath_, std::ios::binary); + if (!file.is_open()) + return 0; + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file.read(chunk, chunkSize) || file.gcount() > 0) + { + size_t bytesRead = file.gcount(); + buffer.append(chunk, bytesRead); + + size_t pos = buffer.find(pattern); + if (pos != std::string::npos) + { + size_t absolutePos = totalRead + pos; // 关键:加上 totalRead + return includePattern ? (absolutePos + pattern.size()) : absolutePos; + } + + if (buffer.size() > pattern.size()) + buffer.erase(0, buffer.size() - pattern.size()); + + totalRead += bytesRead; // 读完后再累计 + } + + return 0; + } + + bool WriteFile::writeAfterPatternOrAppend(const std::string &pattern, const std::string &content) + { + std::lock_guard lock(writeMutex_); + + // 读取整个文件 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + size_t pos = fileData.find(pattern); + if (pos != std::string::npos) + { + // 模式存在,插入位置在模式结尾 + pos += pattern.size(); + + // 删除模式后所有内容 + if (pos < fileData.size()) + fileData.erase(pos); + + // 插入新内容 + fileData.insert(pos, content); + } + else + { + // 模式不存在,直接追加到文件末尾 + if (!fileData.empty() && fileData.back() != '\n') + fileData += '\n'; // 保证换行 + fileData += content; + } + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::overwriteAtPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos >= fileData.size()) + return false; // pos 超过文件范围,无法覆盖 + + // 生成要覆盖的实际数据块 + std::string overwriteBlock; + if (content.size() >= length) + { + overwriteBlock = content.substr(0, length); + } + else + { + overwriteBlock = content; + overwriteBlock.append(length - content.size(), '\0'); // 补齐 + } + + // 计算实际可写范围 + size_t maxWritable = std::min(length, fileData.size() - pos); + + // 覆盖 + fileData.replace(pos, maxWritable, overwriteBlock.substr(0, maxWritable)); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::insertAfterPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos > fileData.size()) + pos = fileData.size(); // 如果 pos 超出范围,就视为文件末尾 + + // 生成要插入的实际数据块 + std::string insertBlock; + if (content.size() >= length) + { + insertBlock = content.substr(0, length); // 只取前 length 个字节 + } + else + { + insertBlock = content; // 全部内容 + insertBlock.append(length - content.size(), '\0'); // 补足空字节 + } + + // 插入到 pos 后面 + fileData.insert(pos + 1, insertBlock); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ReadFile::ReadFile(const std::string &filename) : filename_(filename) {} + + ReadFile::~ReadFile() + { + // std::lock_guard lock(mtx_); + Close(); + } + + bool ReadFile::Open() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + file_.close(); + file_.open(filename_, std::ios::in | std::ios::binary); + return file_.is_open(); + } + + void ReadFile::Close() + { + if (file_.is_open()) + { + std::lock_guard lock(mtx_); + file_.close(); + } + } + + bool ReadFile::IsOpen() const + { + std::lock_guard lock(mtx_); + return file_.is_open(); + } + + std::string ReadFile::ReadAllText() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return ""; + + std::ostringstream ss; + ss << file_.rdbuf(); + return ss.str(); + } + + std::vector ReadFile::ReadAllBinary() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + return ReadBytes(GetFileSize()); + } + + std::vector ReadFile::ReadLines() + { + // std::lock_guard lock(mtx_); + // if (!file_.is_open() && !Open()) + // return {}; + + // std::vector lines; + // std::string line; + // while (std::getline(file_, line)) + // { + // lines.push_back(line); + // } + // return lines; + + // std::lock_guard lock(mtx_); + if (!file_.is_open()) { + file_.open(filename_, std::ios::in | std::ios::binary); + if (!file_.is_open()) return {}; + } + file_.clear(); + file_.seekg(0, std::ios::beg); + + std::vector lines; + std::string line; + while (std::getline(file_, line)) lines.push_back(line); + return lines; + } + + std::vector ReadFile::ReadBytes(size_t count) + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + std::vector buffer(count); + file_.read(buffer.data(), count); + buffer.resize(file_.gcount()); + return buffer; + } + + size_t ReadFile::GetBytesBefore(const std::string &marker, bool includeMarker) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return 0; + + file_.clear(); // 清除EOF和错误状态 + file_.seekg(0, std::ios::beg); // 回到文件开头 + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file_.read(chunk, chunkSize) || file_.gcount() > 0) + { + buffer.append(chunk, file_.gcount()); + size_t pos = buffer.find(marker); + if (pos != std::string::npos) + { + // 如果 includeMarker 为 true,返回包含 marker 的长度 + if (includeMarker) + return pos + marker.size(); + else + return pos; + } + + // 保留末尾部分,避免 buffer 无限增长 + if (buffer.size() > marker.size()) + buffer.erase(0, buffer.size() - marker.size()); + + totalRead += file_.gcount(); + } + + return 0; + } + + std::vector ReadFile::ReadBytesFrom(size_t pos, size_t count) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return {}; + + size_t filesize = GetFileSize(); + if (pos >= filesize) + return {}; // 起始位置超出文件大小 + + file_.clear(); // 清除 EOF 和错误状态 + file_.seekg(pos, std::ios::beg); + if (!file_) + return {}; + + size_t bytes_to_read = count; + if (count == 0 || pos + count > filesize) + bytes_to_read = filesize - pos; // 读取到文件末尾 + + std::vector buffer(bytes_to_read); + file_.read(buffer.data(), bytes_to_read); + + // 实际读取的字节数可能少于请求的数量 + buffer.resize(file_.gcount()); + + return buffer; + } + + bool ReadFile::FileExists() const + { + return std::filesystem::exists(filename_); + } + + size_t ReadFile::GetFileSize() const + { + if (!FileExists()) + return 0; + return std::filesystem::file_size(filename_); + } + + void ReadFile::Reset() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + { + file_.clear(); + file_.seekg(0, std::ios::beg); + } + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals() + { + // 忽略全部的信号 + for (int ii = 1; ii <= 64; ii++) + signal(ii, SIG_IGN); + } + + std::string Ltrim(const std::string &s) + { + size_t start = 0; + while (start < s.size() && std::isspace(static_cast(s[start]))) + { + ++start; + } + return s.substr(start); + } + + std::string Rtrim(const std::string &s) + { + if (s.empty()) + return s; + + size_t end = s.size(); + while (end > 0 && std::isspace(static_cast(s[end - 1]))) + { + --end; + } + return s.substr(0, end); + } + + std::string LRtrim(const std::string &s) + { + return Ltrim(Rtrim(s)); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} \ No newline at end of file diff --git a/DeviceActivate/NetraLib/src/encrypt.cpp b/DeviceActivate/NetraLib/src/encrypt.cpp new file mode 100644 index 0000000..a8aea93 --- /dev/null +++ b/DeviceActivate/NetraLib/src/encrypt.cpp @@ -0,0 +1,91 @@ +#include "encrypt.hpp" +#include + +namespace encrypt +{ + string MD5(const string &info) + { + auto leftrotate = [](uint32_t x, uint32_t c) -> uint32_t { return (x << c) | (x >> (32 - c)); }; + + static const uint32_t s[64] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + static const uint32_t K[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + uint32_t a0 = 0x67452301; + uint32_t b0 = 0xefcdab89; + uint32_t c0 = 0x98badcfe; + uint32_t d0 = 0x10325476; + + std::vector msg(info.begin(), info.end()); + uint64_t bit_len = static_cast(msg.size()) * 8ULL; + msg.push_back(0x80); + while ((msg.size() % 64) != 56) msg.push_back(0x00); + for (int i = 0; i < 8; ++i) msg.push_back(static_cast((bit_len >> (8 * i)) & 0xff)); + + for (size_t offset = 0; offset < msg.size(); offset += 64) + { + uint32_t M[16]; + for (int i = 0; i < 16; ++i) + { + size_t j = offset + i * 4; + M[i] = static_cast(msg[j]) | + (static_cast(msg[j + 1]) << 8) | + (static_cast(msg[j + 2]) << 16) | + (static_cast(msg[j + 3]) << 24); + } + + uint32_t A = a0, B = b0, C = c0, D = d0; + for (uint32_t i = 0; i < 64; ++i) + { + uint32_t F, g; + if (i < 16) { F = (B & C) | ((~B) & D); g = i; } + else if (i < 32) { F = (D & B) | ((~D) & C); g = (5 * i + 1) % 16; } + else if (i < 48) { F = B ^ C ^ D; g = (3 * i + 5) % 16; } + else { F = C ^ (B | (~D)); g = (7 * i) % 16; } + + F = F + A + K[i] + M[g]; + A = D; + D = C; + C = B; + B = B + leftrotate(F, s[i]); + } + + a0 += A; b0 += B; c0 += C; d0 += D; + } + + uint8_t digest[16]; + auto u32_to_le = [](uint32_t v, uint8_t out[4]) { + out[0] = static_cast(v & 0xff); + out[1] = static_cast((v >> 8) & 0xff); + out[2] = static_cast((v >> 16) & 0xff); + out[3] = static_cast((v >> 24) & 0xff); + }; + u32_to_le(a0, digest + 0); + u32_to_le(b0, digest + 4); + u32_to_le(c0, digest + 8); + u32_to_le(d0, digest + 12); + + static const char *hex = "0123456789abcdef"; + std::string out; + out.resize(32); + for (int i = 0; i < 16; ++i) + { + out[i * 2] = hex[(digest[i] >> 4) & 0x0f]; + out[i * 2 + 1] = hex[digest[i] & 0x0f]; + } + return out; + } +} \ No newline at end of file diff --git a/DeviceActivate/bin/init b/DeviceActivate/bin/init new file mode 100755 index 0000000..b1247b6 Binary files /dev/null and b/DeviceActivate/bin/init differ diff --git a/DeviceActivate/src/main.cpp b/DeviceActivate/src/main.cpp new file mode 100644 index 0000000..bbbd0f1 --- /dev/null +++ b/DeviceActivate/src/main.cpp @@ -0,0 +1,364 @@ +/* +本程序用于设备激活 +当检测配置文件发现设备未激活时,则系统阻塞 +进行激活,并保存密文 +当检测到设备已被激活后,启动验证程序,并退出本程序 +*/ + +#include +#include //和App进行MQTT数据交互 +#include +#include +#include + +#include //用于操作JSON文件 + +#include "Netra.hpp" + +using namespace std; +using namespace QCL; +using namespace chrono_literals; + +// 配置相对路径 +const string envPath = "/home/orangepi/RKApp/InitAuth/conf/.env"; + +// MQTT相关配置 +const string mqtt_url = "tcp://192.168.12.1:1883"; +const string clientId = "RK3588_SubTest"; +const string TopicRecv = "/bsd_camera/cmd"; // 接收手机传来的信息 +const string TopicSend = "/bsd_camera/init"; // 发送的话题 +const int Qos = 1; + +atomic isRunning(true); // 是否需要激活 +atomic Deactivate(false); // 激活标志 +mutex envMutex; // 配置锁 +mutex runMutex; +condition_variable runCv; // 条件变量,判断是否可以启动验证程序 + +mqtt::async_client client(mqtt_url, clientId); +atomic g_soft_pid{0}; + +// 获取SIM卡号 +// 通过串口发送 AT+CCID 指令,读取并解析返回的ICCID号 +string GetSimICCID(const string &tty = "/dev/ttyUSB2"); +// 获取唯一标识码 +string GetqrCode(); + +// MQTT初始化 +void mqttInit(); + +// 连接成功的回调 +void connectCallback(const string &cause); + +// 接受消息的回调 +void messageCallback(mqtt::const_message_ptr msg); + +// 检测设备是否已进行初始化 +bool checkUUID(); + +// 退出程序,开始进行设备验证 +void StartCheck(); + +void forwardSigint(int); + +int main() +{ + if (checkUUID()) + { + // 设备已被激活,调用验证程序,退出本程序 + cout << "设备已激活,开始验证" << endl; + StartCheck(); + return 0; + } + + // 初始化MQTT + mqttInit(); + + unique_lock lk(runMutex); + runCv.wait(lk, []() + { return !isRunning.load(); }); + return 0; +} + +void forwardSigint(int) +{ + pid_t gpid = g_soft_pid.load(); + if (gpid > 0) + { + kill(-gpid, SIGINT); + } +} + +// 获取唯一标识码 +string GetqrCode() +{ + lock_guard lk(envMutex); + string UUID = ""; + ReadFile rf(envPath); + rf.Open(); + auto lines = rf.ReadLines(); + for (auto &line : lines) + { + if (line.find("UUID:\"") != string::npos) + { + size_t start = sizeof("UUID:\"") - 1; + size_t end = line.find_last_of("\"") - start; + UUID = line.substr(start, end); + } + } + rf.Close(); + return UUID; +} + +// 退出程序,开始进行设备验证 +void StartCheck() +{ + namespace bp = boost::process; + string cmd = "../../softWareInit/bin/verification "; + cout << cmd << endl; + try + { + bp::child c(cmd); + g_soft_pid = c.id(); + signal(SIGINT, forwardSigint); + c.wait(); // 等待子进程结束 + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + string sysCmd = cmd + " &"; + system(sysCmd.c_str()); + } + + // 退出程序 + isRunning = false; + runCv.notify_one(); +} + +// 检测设备是否已经激活 +bool checkUUID() +{ + bool flag = false; + + // 读取文件 + ReadFile rf(envPath); + if (rf.Open() == false) + { + cerr << "文件打开失败" << endl; + } + + // 读取文本每一行 + auto lines = rf.ReadLines(); + for (auto &ii : lines) + { + if (ii.find("ServerPwd:null") != string::npos) + { + flag = false; + break; + } + else + flag = true; + } + + rf.Close(); + return flag; +} + +// 接受消息的回调 +void messageCallback(mqtt::const_message_ptr msg) +{ + // 接受App传来的密文,并保存在配置文件中 + auto buffer = msg->to_string(); + cout << "Topic:" << msg->get_topic() << ",msg:" << buffer << endl; + if (buffer.find("Activate") != string::npos) + { + // 接受请求,发送SIM卡号 + string ICCID = GetSimICCID(); + string qrcode = GetqrCode(); + string deviceId = format(R"({"cardNo":"{}","qrcode":"{}"})", ICCID, qrcode); + cout << "deviceId:" << deviceId << "" << endl; + client.publish(TopicSend, deviceId, Qos, false); // 不保存 + } + else if (buffer.find("ServerPwd") != string::npos) + { + // 接受UUID,保存至配置文件中,退出程序,调用设备验证程序 + auto res = nlohmann::json::parse(buffer); // 准备解析接受到的秘钥 + auto pwd = res["Data"]; + cout << "pass = " << pwd << endl; + // 写入文件 + ReadFile rf(envPath); + rf.Open(); + + auto lines = rf.ReadLines(); + + for (auto &ii : lines) + { + if (ii.find("ServerPwd:null") != string::npos) + ii = format("ServerPwd:{}", pwd); + } + rf.Close(); + + thread([lines]() + { + lock_guard lk(envMutex); + + WriteFile wf(envPath); + string out; + out.reserve(1024); + for (size_t i = 0; i < lines.size(); ++i) + { + out += lines[i]; + if (i + 1 < lines.size()) + out += "\n"; + } + wf.overwriteText(out); + StartCheck(); }) + .detach(); + } +} + +// 连接成功的回调 +void connectCallback(const string &cause) +{ + cout << "连接成功" << endl; +} + +void mqttInit() +{ + client.set_message_callback(messageCallback); + client.set_connected_handler(connectCallback); + + client.connect()->wait(); // 进行连接 + client.subscribe(TopicRecv, Qos)->wait(); // 订阅话题 +} + +// 获取SIM卡号 +// 通过串口发送 AT+CCID 指令,读取并解析返回的ICCID号 +string GetSimICCID(const string &tty) +{ + int retry = 0; + while (retry < 5) + { + // 非阻塞打开,避免因 VMIN/VTIME 卡死 + int fd = open(tty.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); + if (fd < 0) + { + std::cerr << "无法打开串口: " << tty << std::endl; + return ""; + } + + // 原始模式配置 + struct termios options{}; + tcgetattr(fd, &options); + cfsetispeed(&options, B115200); + cfsetospeed(&options, B115200); + options.c_cflag |= (CLOCAL | CREAD); + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_oflag &= ~OPOST; + options.c_iflag &= ~(IXON | IXOFF | IXANY); + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = 5; // 0.5s + tcsetattr(fd, TCSANOW, &options); + + auto send_and_read = [&](const char *cmd) -> std::string + { + // 清空缓冲并发送 + tcflush(fd, TCIOFLUSH); + write(fd, cmd, strlen(cmd)); + std::string result; + char buf[256] = {0}; + // 轮询读取,累计约2秒 + for (int i = 0; i < 20; ++i) + { + int n = read(fd, buf, sizeof(buf)); + if (n > 0) + result.append(buf, n); + usleep(100000); + } + return result; + }; + + // 先试 AT+QCCID,失败再试 AT+CCID + std::string result = send_and_read("AT+QCCID\r\n"); + if (result.find("+QCCID") == std::string::npos) + { + std::string r2 = send_and_read("AT+CCID\r\n"); + if (!r2.empty()) + result += r2; + } + close(fd); + + // 打印原始回应便于调试 + std::string debug = result; + // 清理换行 + debug.erase(std::remove_if(debug.begin(), debug.end(), + [](unsigned char c) + { return c == '\r' || c == '\n'; }), + debug.end()); + // std::cout << "ICCID原始回应: " << debug << std::endl; + + // 错误重试 + if (result.find("ERROR") != std::string::npos) + { + retry++; + usleep(200000); + continue; + } + + // 解析(支持字母数字) + std::smatch m; + // +QCCID 或 +CCID 后取字母数字 + std::regex reg(R"(\+(?:Q)?CCID:\s*([0-9A-Za-z]+))"); + if (std::regex_search(debug, m, reg)) + { + std::string iccid = m[1]; + // 去掉尾部OK或非字母数字 + while (!iccid.empty() && !std::isalnum(static_cast(iccid.back()))) + iccid.pop_back(); + if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK") + iccid.erase(iccid.size() - 2); + return iccid; + } + + // 兜底:19~22位的字母数字(如尾部含 D) + std::regex reg2(R"(([0-9A-Za-z]{19,22}))"); + if (std::regex_search(debug, m, reg2)) + { + std::string iccid = m[1]; + while (!iccid.empty() && !std::isalnum(static_cast(iccid.back()))) + iccid.pop_back(); + if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK") + iccid.erase(iccid.size() - 2); + return iccid; + } + + // 进一步兜底:手工截取 +QCCID: / +CCID: 后的连续字母数字 + auto parse_after = [&](const std::string &s, const std::string &key) -> std::string + { + size_t pos = s.find(key); + if (pos == std::string::npos) + return ""; + pos += key.size(); + while (pos < s.size() && std::isspace(static_cast(s[pos]))) + pos++; + size_t start = pos; + while (pos < s.size() && std::isalnum(static_cast(s[pos]))) + pos++; + std::string iccid = (pos > start) ? s.substr(start, pos - start) : ""; + if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK") + iccid.erase(iccid.size() - 2); + return iccid; + }; + { + std::string iccid = parse_after(debug, "+QCCID:"); + if (iccid.empty()) + iccid = parse_after(debug, "+CCID:"); + if (!iccid.empty()) + return iccid; + } + + retry++; + usleep(200000); + } + return ""; +} \ No newline at end of file diff --git a/DeviceActivate/src/makefile b/DeviceActivate/src/makefile new file mode 100644 index 0000000..56cda4a --- /dev/null +++ b/DeviceActivate/src/makefile @@ -0,0 +1,8 @@ +all:init + +init:main.cpp + g++ -g -o init main.cpp -lpaho-mqttpp3 -lpaho-mqtt3a -lpthread /home/orangepi/RKApp/DeviceActivate/NetraLib/src/Netra.cpp -I/home/orangepi/RKApp/DeviceActivate/NetraLib/include + mv init ../bin/ + +clean: + rm -rf ../bin/init \ No newline at end of file diff --git a/FastApi/__pycache__/fastApi.cpython-38.pyc b/FastApi/__pycache__/fastApi.cpython-38.pyc new file mode 100644 index 0000000..72f426c Binary files /dev/null and b/FastApi/__pycache__/fastApi.cpython-38.pyc differ diff --git a/FastApi/fastApi.py b/FastApi/fastApi.py new file mode 100644 index 0000000..05c96c6 --- /dev/null +++ b/FastApi/fastApi.py @@ -0,0 +1,245 @@ +#本程序用于启用fastApi,用于与摄像头的数据交互建立连接 + +from fastapi import FastAPI, File, UploadFile, Form, WebSocket, WebSocketDisconnect +from fastapi.responses import JSONResponse +import os, shutil, subprocess, json, time, threading, asyncio + +app = FastAPI() + +VIDEO_SAVE_PATH = "/mnt/save/video" +IMAGE_SAVE_PATH = "/mnt/save/warning" +MODBUS_BIN_PATH = "/home/orangepi/RKApp/GPIOSignal/bin/sendGpioSignal" +GPIO_CONFIG_FILE = "/home/orangepi/RKApp/InitAuth/conf/.env" +Camera_Config_File = "/opt/rknn-yolov11/.env" + +os.makedirs(VIDEO_SAVE_PATH, exist_ok=True) +os.makedirs(IMAGE_SAVE_PATH, exist_ok=True) + +# ============================== GPIO 状态控制器 ============================== # + +# 全局状态变量 +current_gpio_state = 'HIGH' # 当前GPIO状态 ('HIGH' 或 'LOW') +last_person_time = 0.0 # 最后一次检测到人的时间 +gpio_state_lock = threading.Lock() # 线程锁 +GPIO_DELAY_SECONDS = 2.0 # 人离开后的延迟时间(秒) + +def update_gpio_config(value: str): + """ + 更新GPIO配置文件中的 outPutMode 值 + + Args: + value: 'true' 或 'false' + """ + try: + with open(GPIO_CONFIG_FILE, 'r', encoding='utf-8') as f: + lines = f.readlines() + + modified = False + for i, line in enumerate(lines): + if line.strip().startswith('outPutMode'): + lines[i] = f"outPutMode:{value}\n" + modified = True + break + + if not modified: + lines.append(f"outPutMode:{value}\n") + + with open(GPIO_CONFIG_FILE, 'w', encoding='utf-8') as f: + f.writelines(lines) + + print(f"[GPIO] 配置文件已更新: outPutMode={value}") + return True + except Exception as e: + print(f"[GPIO ERROR] 更新配置文件失败: {e}") + return False + +def call_gpio_program(): + """调用GPIO控制程序""" + try: + signal = "echo 'orangepi' | sudo " + MODBUS_BIN_PATH + result = subprocess.run([signal], shell=True, capture_output=True, text=True, timeout=5) + if result.returncode == 0: + print(f"[GPIO] GPIO程序调用成功") + return True + else: + print(f"[GPIO ERROR] GPIO程序调用失败: {result.stderr}") + return False + except Exception as e: + print(f"[GPIO ERROR] 调用GPIO程序时发生异常: {e}") + return False + +def set_gpio_low(): + """设置GPIO为低电平""" + global current_gpio_state + if update_gpio_config('false') and call_gpio_program(): + current_gpio_state = 'LOW' + print(f"[GPIO] ✅ 已切换到低电平 (检测到人)") + return True + return False + +def set_gpio_high(): + """设置GPIO为高电平""" + global current_gpio_state + if update_gpio_config('true') and call_gpio_program(): + current_gpio_state = 'HIGH' + print(f"[GPIO] ✅ 已恢复高电平 (延迟{GPIO_DELAY_SECONDS}秒)") + return True + return False + +def gpio_monitor_task(): + """ + GPIO监控后台任务 + 定期检查是否需要恢复高电平 + """ + global current_gpio_state, last_person_time + + print("[GPIO] 🚀 GPIO监控线程已启动") + + while True: + time.sleep(0.5) # 每0.5秒检查一次 + + with gpio_state_lock: + current_time = time.time() + time_since_last_person = current_time - last_person_time + + # 如果当前是低电平,且距离上次检测到人已超过延迟时间 + if (current_gpio_state == 'LOW' and + last_person_time > 0 and + time_since_last_person >= GPIO_DELAY_SECONDS): + + print(f"[GPIO] ⏰ 距上次检测: {time_since_last_person:.1f}秒,准备恢复高电平") + set_gpio_high() + +# 启动GPIO监控线程 +gpio_monitor_thread = threading.Thread(target=gpio_monitor_task, daemon=True) +gpio_monitor_thread.start() + + +@app.websocket("/ws/distance") +@app.websocket("/ws/distance/") +async def websocket_distance(websocket: WebSocket): + global current_gpio_state, last_person_time + + await websocket.accept() + print("✅ WebSocket 客户端已连接") + try: + while True: + data = await websocket.receive_text() + try: + msg = json.loads(data) + distance = msg.get("distance") + ts = msg.get("ts") + print(f"📨 收到距离: {distance}m, 时间戳: {ts}") + + # # 写入日志,单独try,避免异常影响后续流程 + # try: + # now = datetime.now() + # log_line = f"{now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} distance={distance} ts={ts}\n" + # with open("/home/orangepi/Opencv/time.txt", "a") as f: + # f.write(log_line) + # except Exception as log_e: + # print(f"日志写入失败: {log_e}") + + # ⭐ GPIO状态控制逻辑 + with gpio_state_lock: + current_time = time.time() + + # 读取配置文件中的危险距离阈值 + danger_distance = 3.0 # 默认值 + try: + with open(GPIO_CONFIG_FILE, 'r', encoding='utf-8') as f: + lines = f.readlines() + for line in lines: + if line.strip().startswith('MAX_DISTANCE'): + danger_distance = float(line.strip().split(':')[1]) + break + except Exception as e: + print(f"[Config] ⚠️ 读取危险距离失败,使用默认值3.0m: {e}") + + print(f"[Config] 📏 危险距离阈值: {danger_distance}m, 当前距离: {distance}m") + + # 判断是否在危险区域内 + if distance is not None and distance < danger_distance: + # 人在危险区域内,更新时间戳 + last_person_time = current_time + print(f"[GPIO] ⚠️ 危险!距离 {distance:.2f}m < {danger_distance:.2f}m") + + # 如果当前是高电平,切换到低电平 + if current_gpio_state == 'HIGH': + print(f"[GPIO] 🔻 当前为高电平,准备切换到低电平") + set_gpio_low() + else: + print(f"[GPIO] ⚡ 当前已是低电平,保持状态") + else: + # 人在安全区域,不触发GPIO + print(f"[GPIO] ✅ 安全距离 {distance:.2f}m >= {danger_distance:.2f}m,不触发GPIO") + + # # 发送响应给客户端 + # await websocket.send_json({ + # "status": "success", + # "distance": distance, + # "gpio_state": current_gpio_state, + # "message": f"已检测到人,GPIO状态: {current_gpio_state}" + # }) + + except json.JSONDecodeError: + await websocket.send_text("invalid JSON") + except Exception as e: + await websocket.send_text(f"server error: {e}") + print(f"❌ 处理消息时出错: {e}") + + except WebSocketDisconnect: + print("⚠️ WebSocket 客户端断开连接") + except Exception as e: + print("❌ WebSocket 处理出错:", e) + +@app.post("/upload_video/") +async def upload_video( + video: UploadFile = File(..., description="上传的视频(.mp4)") +): + if not video.filename.lower().endswith('.mp4'): + return JSONResponse(status_code=400, content={"error": "视频必须为.mp4格式"}) + video_path = os.path.join(VIDEO_SAVE_PATH, video.filename) + with open(video_path, "wb") as vid_file: + shutil.copyfileobj(video.file, vid_file) + return {"video_saved_to": video_path} + + +@app.post("/upload_image/") +async def upload_image( + image: UploadFile = File(..., description="上传的图片(.jpg)") +): + if not image.filename.lower().endswith('.jpg'): + return JSONResponse(status_code=400, content={"error": "图片必须为.jpg格式"}) + image_path = os.path.join(IMAGE_SAVE_PATH, image.filename) + with open(image_path, "wb") as img_file: + shutil.copyfileobj(image.file, img_file) + return {"image_saved_to": image_path} + + +@app.post("/upload_distance/") +async def upload_distance( + distance: float = Form(..., description="距离") +): + # 调用本地 modbus 程序,将距离作为参数传递 + try: + #写入日志精确到毫秒 + # now = datetime.now() + # log_line = f"{now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} distance={distance} ts={ts}\n" + # with open("/home/orangepi/Opencv/time.txt", "a") as f: + # f.write(log_line) + result = subprocess.run( + [MODBUS_BIN_PATH, str(int(distance))], + capture_output=True, + text=True, + timeout=5 + ) + if result.returncode != 0: + return JSONResponse(status_code=500, content={"error": f"modbus程序执行失败: {result.stderr}"}) + return { + "distance": distance, + "modbus_output": result.stdout.strip() + } + except Exception as e: + return JSONResponse(status_code=500, content={"error": str(e)}) + diff --git a/FastApi/fastApi.py.bak b/FastApi/fastApi.py.bak new file mode 100644 index 0000000..0622706 --- /dev/null +++ b/FastApi/fastApi.py.bak @@ -0,0 +1,121 @@ +#本程序用于启用fastApi,用于与摄像头的数据交互建立连接 + +from fastapi import FastAPI, File, UploadFile, Form, WebSocket, WebSocketDisconnect +from fastapi.responses import JSONResponse +import os, shutil, subprocess, json + +app = FastAPI() + +VIDEO_SAVE_PATH = "/mnt/save/video" +IMAGE_SAVE_PATH = "/mnt/save/warning" +MODBUS_BIN_PATH = "/home/orangepi/RKApp/GPIOSignal/bin/sendGpioSignal" + +os.makedirs(VIDEO_SAVE_PATH, exist_ok=True) +os.makedirs(IMAGE_SAVE_PATH, exist_ok=True) + + +@app.websocket("/ws/distance") +@app.websocket("/ws/distance/") +async def websocket_distance(websocket: WebSocket): + await websocket.accept() + print("✅ WebSocket 客户端已连接") + try: + while True: + data = await websocket.receive_text() + try: + msg = json.loads(data) + distance = msg.get("distance") + ts = msg.get("ts") + print(f"收到距离: {distance}, 时间戳: {ts}") + + # # 写入日志,单独try,避免异常影响后续流程 + # try: + # now = datetime.now() + # log_line = f"{now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} distance={distance} ts={ts}\n" + # with open("/home/orangepi/Opencv/time.txt", "a") as f: + # f.write(log_line) + # except Exception as log_e: + # print(f"日志写入失败: {log_e}") + #调用 Modbus 可执行文件 + # print(f"调用 Modbus 程序,传递距离: {distance}") + signal = "echo 'orangepi' | sudo "+ MODBUS_BIN_PATH + result = subprocess.run( + [signal], + # shell=True,ocapture_output=True, text=True, timeout=0 + shell=True + ) + # print("signal:", signal) + # print(f"Modbus 程序返回: {result.stdout.strip()}, 错误: {result.stderr.strip()}") + if result.returncode != 0: + # 发送错误信息给客户端 + await websocket.send_json({ + "error": f"modbus 程序执行失败: {result.stderr.strip()}" + }) + continue + + # 正常发送结果 + await websocket.send_json({ + "distance": distance, + "modbus_output": result.stdout.strip() + }) + + except json.JSONDecodeError: + await websocket.send_text("invalid JSON") + except Exception as e: + await websocket.send_text(f"server error: {e}") + + except WebSocketDisconnect: + print("⚠️ WebSocket 客户端断开连接") + except Exception as e: + print("❌ WebSocket 处理出错:", e) + +@app.post("/upload_video/") +async def upload_video( + video: UploadFile = File(..., description="上传的视频(.mp4)") +): + if not video.filename.lower().endswith('.mp4'): + return JSONResponse(status_code=400, content={"error": "视频必须为.mp4格式"}) + video_path = os.path.join(VIDEO_SAVE_PATH, video.filename) + with open(video_path, "wb") as vid_file: + shutil.copyfileobj(video.file, vid_file) + return {"video_saved_to": video_path} + + +@app.post("/upload_image/") +async def upload_image( + image: UploadFile = File(..., description="上传的图片(.jpg)") +): + if not image.filename.lower().endswith('.jpg'): + return JSONResponse(status_code=400, content={"error": "图片必须为.jpg格式"}) + image_path = os.path.join(IMAGE_SAVE_PATH, image.filename) + with open(image_path, "wb") as img_file: + shutil.copyfileobj(image.file, img_file) + return {"image_saved_to": image_path} + + +@app.post("/upload_distance/") +async def upload_distance( + distance: float = Form(..., description="距离") +): + # 调用本地 modbus 程序,将距离作为参数传递 + try: + #写入日志精确到毫秒 + # now = datetime.now() + # log_line = f"{now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} distance={distance} ts={ts}\n" + # with open("/home/orangepi/Opencv/time.txt", "a") as f: + # f.write(log_line) + result = subprocess.run( + [MODBUS_BIN_PATH, str(int(distance))], + capture_output=True, + text=True, + timeout=5 + ) + if result.returncode != 0: + return JSONResponse(status_code=500, content={"error": f"modbus程序执行失败: {result.stderr}"}) + return { + "distance": distance, + "modbus_output": result.stdout.strip() + } + except Exception as e: + return JSONResponse(status_code=500, content={"error": str(e)}) + diff --git a/GPIOSignal/bin/sendGpioSignal b/GPIOSignal/bin/sendGpioSignal new file mode 100755 index 0000000..ac15ff5 Binary files /dev/null and b/GPIOSignal/bin/sendGpioSignal differ diff --git a/GPIOSignal/src/makefile b/GPIOSignal/src/makefile new file mode 100644 index 0000000..83a452d --- /dev/null +++ b/GPIOSignal/src/makefile @@ -0,0 +1,9 @@ +all:sendGpioSignal + +sendGpioSignal: + g++ -g -o sendGpioSignal sendGpioSignal.cpp -lwiringPi + + mv sendGpioSignal ../bin + +clean: + rm -rf sendGpioSignal \ No newline at end of file diff --git a/GPIOSignal/src/sendGpioSignal.cpp b/GPIOSignal/src/sendGpioSignal.cpp new file mode 100644 index 0000000..fcac4d6 --- /dev/null +++ b/GPIOSignal/src/sendGpioSignal.cpp @@ -0,0 +1,62 @@ +/* + 本程序用于控制引脚输出高低电平 +*/ + +#include +#include +#include + +using namespace std; + +// 初始化GPIO引脚 +int InitGpio(int GPIO_Pin1, int GPIO_Pin2); + +// 写入GPIO引脚 +void WriteGpio(int GPIO_Pin, int value); + +int main(int argc, char *argv[]) +{ + int GPIO_Pin1 = 7; + int GPIO_Pin2 = 8; + + if (argc < 2) + { + cerr << "传入参数错误" << endl; + return -1; + } + + // 初始化GPIO引脚 + if (InitGpio(GPIO_Pin1, GPIO_Pin2) != 0) + { + cout << "Error: Failed to initialize GPIO pin " << endl; + return 1; + } + + WriteGpio(GPIO_Pin1, stoi(argv[1])); + WriteGpio(GPIO_Pin2, stoi(argv[1])); + + return 0; +} + +// 初始化GPIO引脚 +int InitGpio(int GPIO_Pin1, int GPIO_Pin2) +{ + // 使用物理引脚编号,确保与实际排针一致 + if (wiringPiSetupPhys() != 0) + { + return -1; // 初始化失败 + } + + pinMode(GPIO_Pin1, OUTPUT); + pinMode(GPIO_Pin2, OUTPUT); + digitalWrite(GPIO_Pin1, LOW); // 默认设置为低电平 + digitalWrite(GPIO_Pin2, LOW); // 默认设置为低电平 + + return 0; // 初始化成功 +} + +// 写入GPIO引脚 +void WriteGpio(int GPIO_Pin, int value) +{ + digitalWrite(GPIO_Pin, value == 1 ? HIGH : LOW); +} \ No newline at end of file diff --git a/GPIOSignal/src/sendGpioSignal.cpp.bak b/GPIOSignal/src/sendGpioSignal.cpp.bak new file mode 100644 index 0000000..6435b04 --- /dev/null +++ b/GPIOSignal/src/sendGpioSignal.cpp.bak @@ -0,0 +1,113 @@ +/* + 本程序用于读取配置文件 + 根据配置文件发送高低电平 + 发送引脚固定:7,8 +*/ + +#include +#include +#include + +#include "/home/orangepi/RKApp/softWareInit/NetraLib/include/Netra.hpp" + +using namespace std; +using namespace QCL; + +/* +Parmas: + argv[1]: GPIO引脚编号 + argv[2]: 设置引脚为高/低电平 +*/ + +const string SetFile = "/home/orangepi/InitAuth/conf/.env"; + +// 初始化GPIO引脚 +int InitGpio(int GPIO_Pin1, int GPIO_Pin2); + +// 写入GPIO引脚 +void WriteGpio(int GPIO_Pin, int value); + +// 获取输出模式 +bool GetOutValue(int &value); + +int main(int argc, char *argv[]) +{ + int GPIO_Pin1 = 7; + int GPIO_Pin2 = 8; + + int value = 0; + cout << "[sendGpioSignal] 启动,读取配置: " << SetFile << endl; + if (GetOutValue(value) == false) + { + cerr << "[sendGpioSignal] 未读取到 outPutMode,程序退出" << endl; + return -1; + } + cout << "[sendGpioSignal] 读取到 outPutMode=" << (value == 1 ? "true" : "false") << endl; + + // 初始化GPIO引脚 + if (InitGpio(GPIO_Pin1, GPIO_Pin2) != 0) + { + cout << "Error: Failed to initialize GPIO pin " << endl; + return 1; + } + // 写入GPIO引脚 + cout << "[sendGpioSignal] 设置 GPIO(" << GPIO_Pin1 << "," << GPIO_Pin2 << ") 为 " + << (value == 1 ? "HIGH" : "LOW") << endl; + WriteGpio(GPIO_Pin1, value); + WriteGpio(GPIO_Pin2, value); + cout << "[sendGpioSignal] 完成" << endl; + + this_thread::sleep_for(chrono::milliseconds(100)); + + WriteGpio(GPIO_Pin1, !value); + WriteGpio(GPIO_Pin2, !value); + + return 0; +} + +// 获取输出模式 +bool GetOutValue(int &value) +{ + bool flag = true; + // 读取文件 + ReadFile *rf =new ReadFile(SetFile); + if (rf->Open() == false) + { + cerr << "读取文件失败" << endl; + flag = false; + } + + auto str = rf->ReadLines(); + for (auto &ii : str) + { + if (ii.find("outPutMode") != string::npos) + { + value = (ii.substr(string("outPutMode:").size()) == "true" ? 1 : 0); + } + } + + return flag; +} + +// 初始化GPIO引脚 +int InitGpio(int GPIO_Pin1, int GPIO_Pin2) +{ + // 使用物理引脚编号,确保与实际排针一致 + if (wiringPiSetupPhys() != 0) + { + return -1; // 初始化失败 + } + + pinMode(GPIO_Pin1, OUTPUT); + pinMode(GPIO_Pin2, OUTPUT); + digitalWrite(GPIO_Pin1, LOW); // 默认设置为低电平 + digitalWrite(GPIO_Pin2, LOW); // 默认设置为低电平 + + return 0; // 初始化成功 +} + +// 写入GPIO引脚 +void WriteGpio(int GPIO_Pin, int value) +{ + digitalWrite(GPIO_Pin, value == 1 ? HIGH : LOW); +} \ No newline at end of file diff --git a/GPIOSignal/src/sendGpioSignal.cpp.bak2 b/GPIOSignal/src/sendGpioSignal.cpp.bak2 new file mode 100644 index 0000000..4d8d14c --- /dev/null +++ b/GPIOSignal/src/sendGpioSignal.cpp.bak2 @@ -0,0 +1,116 @@ +/* + 本程序用于控制引脚输出高低电平 +*/ + +#include +#include +#include + +#include "/home/orangepi/RKApp/softWareInit/NetraLib/include/Netra.hpp" + +using namespace std; +using namespace QCL; + +const string SetFile = "/home/orangepi/RKApp/InitAuth/conf/.env"; + +// 初始化GPIO引脚 +int InitGpio(int GPIO_Pin1, int GPIO_Pin2); + +// 写入GPIO引脚 +void WriteGpio(int GPIO_Pin, int value); + +// 获取输出模式 +bool GetOutValue(int &value); + +int main(int argc, char *argv[]) +{ + int GPIO_Pin1 = 7; + int GPIO_Pin2 = 8; + + int value = 0; + bool useArg = false; + + // 修改点:如果传入了参数,直接使用参数作为电平值 (1 或 0) + if (argc > 1) + { + value = atoi(argv[1]); + useArg = true; + cout << "[sendGpioSignal] 使用命令行参数: " << value << endl; + } + else + { + // 原有的读取文件逻辑 + cout << "[sendGpioSignal] 启动,读取配置: " << SetFile << endl; + if (GetOutValue(value) == false) + { + cerr << "[sendGpioSignal] 未读取到 outPutMode,程序退出" << endl; + return -1; + } + cout << "[sendGpioSignal] 读取到 outPutMode=" << (value == 1 ? "true" : "false") << endl; + } + + // 初始化GPIO引脚 + if (InitGpio(GPIO_Pin1, GPIO_Pin2) != 0) + { + cout << "Error: Failed to initialize GPIO pin " << endl; + return 1; + } + // 写入GPIO引脚 + cout << "[sendGpioSignal] 设置 GPIO(" << GPIO_Pin1 << "," << GPIO_Pin2 << ") 为 " + << (value == 1 ? "HIGH" : "LOW") << endl; + WriteGpio(GPIO_Pin1, value); + WriteGpio(GPIO_Pin2, value); + cout << "[sendGpioSignal] 完成" << endl; + + this_thread::sleep_for(chrono::milliseconds(100)); + + return 0; +} + +// 获取输出模式 +bool GetOutValue(int &value) +{ + bool flag = true; + // 读取文件 + ReadFile rf(SetFile); + if (rf.Open() == false) + { + cerr << "读取文件失败" << endl; + flag = false; + } + + auto str = rf.ReadLines(); + for (auto &ii : str) + { + if (ii.find("outPutMode") != string::npos) + { + value = (ii.substr(string("outPutMode:").size()) == "true" ? 1 : 0); + } + } + + rf.Close(); + return flag; +} + +// 初始化GPIO引脚 +int InitGpio(int GPIO_Pin1, int GPIO_Pin2) +{ + // 使用物理引脚编号,确保与实际排针一致 + if (wiringPiSetupPhys() != 0) + { + return -1; // 初始化失败 + } + + pinMode(GPIO_Pin1, OUTPUT); + pinMode(GPIO_Pin2, OUTPUT); + digitalWrite(GPIO_Pin1, LOW); // 默认设置为低电平 + digitalWrite(GPIO_Pin2, LOW); // 默认设置为低电平 + + return 0; // 初始化成功 +} + +// 写入GPIO引脚 +void WriteGpio(int GPIO_Pin, int value) +{ + digitalWrite(GPIO_Pin, value == 1 ? HIGH : LOW); +} \ No newline at end of file diff --git a/GetNet/bin/setNet b/GetNet/bin/setNet new file mode 100755 index 0000000..56e8e7f Binary files /dev/null and b/GetNet/bin/setNet differ diff --git a/GetNet/src/GetNet.cpp b/GetNet/src/GetNet.cpp new file mode 100644 index 0000000..120bf7b --- /dev/null +++ b/GetNet/src/GetNet.cpp @@ -0,0 +1,229 @@ +/* +本程序主要用于初始化网络配置,使4g模块可以正常上网 +*/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// 串口设备 +string Serious = "/dev/ttyUSB2"; + +// 设置网络 +void SetNet(); + +int main(int argc, char *argv[]) +{ + + this_thread::sleep_for(chrono::seconds(3)); + + SetNet(); + return 0; +} + +// 设置网络 +void SetNet() +{ + auto configure_serial = [](int fd) -> bool + { + struct termios options; + if (tcgetattr(fd, &options) != 0) + return false; + + cfsetispeed(&options, B115200); + cfsetospeed(&options, B115200); + + options.c_cflag |= (CLOCAL | CREAD); + options.c_cflag &= ~PARENB; // 无校验 + options.c_cflag &= ~CSTOPB; // 1 位停止位 + options.c_cflag &= ~CSIZE; + options.c_cflag |= CS8; // 8 位数据位 + options.c_cflag &= ~CRTSCTS; // 关闭硬件流控 + + options.c_iflag &= ~(IXON | IXOFF | IXANY); // 关闭软件流控 + options.c_iflag &= ~(INLCR | ICRNL); + + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始模式 + options.c_oflag &= ~OPOST; // 原始输出 + + options.c_cc[VMIN] = 0; // 非阻塞读取 + options.c_cc[VTIME] = 10; // 1.0s 读超时 + + if (tcsetattr(fd, TCSANOW, &options) != 0) + return false; + tcflush(fd, TCIOFLUSH); + return true; + }; + + auto write_all = [](int fd, const std::string &data) -> bool + { + const char *p = data.c_str(); + size_t left = data.size(); + while (left > 0) + { + ssize_t n = write(fd, p, left); + if (n < 0) + { + if (errno == EINTR) + continue; + return false; + } + left -= static_cast(n); + p += n; + } + return true; + }; + + auto send_at = [&](int fd, const std::string &cmd, std::string &resp, int timeout_ms = 3000) -> bool + { + std::string line = cmd + "\r\n"; + if (!write_all(fd, line)) + return false; + + resp.clear(); + char buf[256]; + int elapsed = 0; + while (elapsed < timeout_ms) + { + struct pollfd pfd{fd, POLLIN, 0}; + int pr = poll(&pfd, 1, 200); + if (pr > 0 && (pfd.revents & POLLIN)) + { + ssize_t n = read(fd, buf, sizeof(buf)); + if (n > 0) + { + resp.append(buf, buf + n); + if (resp.find("\r\nOK\r\n") != std::string::npos || + resp.find("\nOK\n") != std::string::npos || + resp.find("OK") != std::string::npos) + return true; + if (resp.find("ERROR") != std::string::npos) + return false; + } + } + elapsed += 200; + } + return false; // 超时 + }; + + int fd = open(Serious.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + if (fd < 0) + { + std::cerr << "无法打开串口: " << Serious << "\n"; + return; + } + // 设为阻塞 + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); + + if (!configure_serial(fd)) + { + std::cerr << "串口参数配置失败\n"; + close(fd); + return; + } + + std::string resp; + // 0) 连续握手,确保 AT 可用(最多 10 次) + { + bool ok = false; + for (int i = 1; i <= 10; ++i) + { + if (send_at(fd, "AT", resp, 500)) + { + ok = true; + break; + } + usleep(500 * 1000); + } + if (!ok) + { + std::cerr << "AT 握手失败,模块未响应\n"; + close(fd); + return; + } + } + + // 1) 打开功能 + if (!send_at(fd, "AT+CFUN=1", resp)) + { + std::cerr << "AT+CFUN=1 失败: " << resp << "\n"; + } + else + { + std::cout << "AT+CFUN=1 OK\n"; + } + + // 1.1) 等 SIM 就绪(AT+CPIN? 返回 READY,最多 30 秒) + { + bool ready = false; + for (int i = 0; i < 60; ++i) + { + if (send_at(fd, "AT+CPIN?", resp, 500) && resp.find("READY") != std::string::npos) + { + ready = true; + break; + } + usleep(500 * 1000); + } + if (!ready) + std::cerr << "SIM 未就绪,后续可能失败\n"; + } + + // 2) 设置 PDP 上下文:APN = ipn(请按运营商实际 APN 调整) + if (!send_at(fd, "AT+CGDCONT=1,\"IP\",\"ipn\"", resp)) + { + std::cerr << "AT+CGDCONT 设置失败: " << resp << "\n"; + } + else + { + std::cout << "AT+CGDCONT OK\n"; + } + + // 2.1) 等驻网(AT+CEREG? 返回 ,1 或 ,5,最多 60 秒) + { + auto registered = [](const std::string &s) + { return s.find(",1") != std::string::npos || s.find(",5") != std::string::npos; }; + bool ok = false; + for (int i = 0; i < 60; ++i) + { + if (send_at(fd, "AT+CEREG?", resp, 500) && registered(resp)) + { + ok = true; + break; + } + usleep(1000 * 1000); + } + if (!ok) + std::cerr << "未驻网,继续尝试拨号但成功率较低\n"; + } + + // 3) 打开网卡/拨号(Quectel 私有指令示例)- 失败重试 5 次 + { + bool ok = false; + for (int attempt = 1; attempt <= 5; ++attempt) + { + resp.clear(); + if (send_at(fd, "AT+QNETDEVCTL=1,1,1", resp)) + { + std::cout << "AT+QNETDEVCTL OK (attempt " << attempt << ")\n"; + ok = true; + break; + } + std::cerr << "AT+QNETDEVCTL 失败 (attempt " << attempt << "/5): " << resp << "\n"; + usleep(1000 * 1000); // 1s 间隔 + } + if (!ok) + { + std::cerr << "AT+QNETDEVCTL 连续 5 次失败,退出本步骤\n"; + } + } + + close(fd); +} \ No newline at end of file diff --git a/GetNet/src/makefile b/GetNet/src/makefile new file mode 100644 index 0000000..d120f97 --- /dev/null +++ b/GetNet/src/makefile @@ -0,0 +1,8 @@ +all:SetNet + +SetNet:GetNet.cpp + g++ -g -o setNet GetNet.cpp + mv ./setNet ../bin/ + +clean: + rm -rf ../bin/* \ No newline at end of file diff --git a/Identification/NetraLib/README.md b/Identification/NetraLib/README.md new file mode 100644 index 0000000..c680108 --- /dev/null +++ b/Identification/NetraLib/README.md @@ -0,0 +1,128 @@ +# NetraLib +c/c++基本开发库 + +# TCP 服务端操作 +包括多线程客户端连接,指定客户端数据的收发等等功能 + +# Linux 中屏蔽所有信号操作 +屏蔽所有信号,以防止意外退出 + + +# 写文件操作 +允许原文本进行覆盖写,追加写 +允许二进制进行覆盖写,追加写 +允许在特定位置后面进行插入覆盖操作 +允许删除特定字段后面所有内容在进行写操作 +可以根据需要计算特定符号最后一个字节或者第一个字节所在位置所在位置 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 读文件操作 +支持全文读取(文本和二进制模式) +支持按行读取文本内容 +支持按指定字节数读取数据 +支持计算第一个指定字节序列结束位置(包含该序列本身)的字节数 +提供文件是否存在和文件大小查询 +支持重置文件读取位置,实现多次读取 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 字符串操作 +支持左右空格删除 +支持格式化输出 + + +# Http请求 +提供基于 `cpp-httplib` 的简易 HTTP 客户端封装 `NetRequest`,支持: +1. 同步/异步 GET、POST(JSON、表单) +2. 连接/读写超时设置、Keep-Alive +3. 并发请求上限控制 +4. 可选内存缓存(GET 命中时不发起网络请求) +5. 简单日志回调与性能统计 +6. 断点续传下载到本地文件 + +使用步骤: + +1) 引入头文件,配置目标地址 +```cpp +#include "NetRequest.hpp" + +ntq::RequestOptions opt; +opt.scheme = "http"; // 或 https +opt.host = "127.0.0.1"; // 服务器地址 +opt.port = 8080; // 端口(https 一般 443) +opt.base_path = "/api"; // 可选统一前缀 +opt.connect_timeout_ms = 3000; +opt.read_timeout_ms = 8000; +opt.write_timeout_ms = 8000; +opt.default_headers = { {"Authorization", "Bearer TOKEN"} }; // 可选 + +ntq::NetRequest req(opt); +req.setMaxConcurrentRequests(4); +req.enableCache(std::chrono::seconds(10)); +``` + +2) 发送 GET 请求 +```cpp +auto r = req.Get("/info"); +if (r && r->status == 200) { + // r->body 为返回内容 +} + +// 带查询参数与额外请求头 +httplib::Params q = {{"q","hello"},{"page","1"}}; +httplib::Headers h = {{"X-Req-Id","123"}}; +auto r2 = req.Get("/search", q, h); +``` + +3) 发送 POST 请求 +```cpp +// JSON(Content-Type: application/json) +std::string json = R"({"name":"orangepi","mode":"demo"})"; +auto p1 = req.PostJson("/set", json); + +// 表单(application/x-www-form-urlencoded) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = req.PostForm("/login", form); +``` + +4) 异步调用 +```cpp +auto fut = req.GetAsync("/info"); +auto res = fut.get(); // 与同步用法一致 +``` + +5) 下载到本地(支持断点续传) +```cpp +bool ok = req.DownloadToFile("/files/pkg.bin", "/tmp/pkg.bin", {}, /*resume*/true); +``` + +6) 统计信息 +```cpp +auto s = req.getStats(); +// s.total_requests / s.total_errors / s.last_latency_ms / s.avg_latency_ms +``` + +说明: +- 若使用 HTTPS,需在编译时添加 `-DCPPHTTPLIB_OPENSSL_SUPPORT` 并链接 `-lssl -lcrypto`,且将 `opt.scheme` 设为 `"https"`、端口通常为 `443`。 +- `base_path` 与各函数传入的 `path` 会自动合并,例如 `base_path="/api"` 且 `Get("/info")` 实际请求路径为 `/api/info`。 + +7) 快速用法(无需实例化) +```cpp +using ntq::NetRequest; + +// 直接传完整 URL 发起 GET +auto g = NetRequest::QuickGet("http://127.0.0.1:8080/api/info"); + +// 直接传完整 URL 发起 POST(JSON) +auto p1 = NetRequest::QuickPostJson( + "http://127.0.0.1:8080/api/set", + R"({"name":"orangepi","mode":"demo"})" +); + +// 直接传完整 URL 发起 POST(表单) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = NetRequest::QuickPostForm("http://127.0.0.1:8080/login", form); +``` diff --git a/Identification/NetraLib/include/NetRequest.hpp b/Identification/NetraLib/include/NetRequest.hpp new file mode 100644 index 0000000..3cb473c --- /dev/null +++ b/Identification/NetraLib/include/NetRequest.hpp @@ -0,0 +1,344 @@ +/* +本文件 +网络请求类需要实现以下功能: +1. 发送网络请求 +2. 接收网络响应 +3. 处理网络请求和响应 +4. 实现网络请求和响应的回调函数 +5. 实现网络请求和响应的错误处理 +6. 实现网络请求和响应的日志记录 +7. 实现网络请求和响应的性能统计 +8. 实现网络请求和响应的并发控制 +9. 实现网络请求和响应的缓存管理 +10. 实现网络请求和响应的断点续传 +11. 实现网络请求和响应的断点续传 +12. 实现网络请求和响应的断点续传 +13. 实现网络请求和响应的断点续传 +14. 实现网络请求和响应的断点续传 +*/ +#pragma once +#include "httplib.h" +#include +#include +#include +#include + +// C++17/14 可选类型回退适配:统一使用 ntq::optional / ntq::nullopt +#if defined(__has_include) + #if __has_include() + #include + // 仅当启用了 C++17 或库声明了 optional 功能时,才使用 std::optional + #if defined(__cpp_lib_optional) || (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif +#else + // 无 __has_include:按语言级别判断 + #if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + #include + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #else + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #endif +#endif + +namespace ntq +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief HTTP 响应对象 + * + * 用于承载一次请求返回的状态码、正文与响应头; + * 当 from_cache 为 true 时,表示该响应来自本地缓存(而非实时网络)。 + */ + struct HttpResponse + { + int status = 0; ///< HTTP 状态码(例如 200, 404 等) + std::string body; ///< 响应正文 + httplib::Headers headers; ///< 响应头(大小写不敏感) + bool from_cache = false; ///< 是否来自缓存 + }; + + /** + * @brief 错误码定义 + */ + enum class ErrorCode + { + None = 0, ///< 无错误 + Network, ///< 网络错误(连接失败/发送失败/接收失败等) + Timeout, ///< 超时 + Canceled, ///< 被取消 + InvalidURL, ///< URL 非法 + IOError, ///< 本地 IO 错误(如写文件失败) + SSL, ///< SSL/HTTPS 相关错误 + Unknown ///< 未分类错误 + }; + + /** + * @brief 请求级别的参数配置 + * + * 包含基础连接信息(协议、主机、端口)与默认头部、超时设置等。 + */ + struct RequestOptions + { + std::string scheme = "http"; ///< 协议:http 或 https + std::string host; ///< 目标主机名或 IP(必填) + int port = 80; ///< 端口,http 默认为 80,https 通常为 443 + std::string base_path; ///< 可选的统一前缀(例如 "/api/v1") + + int connect_timeout_ms = 5000; ///< 连接超时(毫秒) + int read_timeout_ms = 10000; ///< 读取超时(毫秒) + int write_timeout_ms = 10000; ///< 写入超时(毫秒) + bool keep_alive = true; ///< 是否保持连接(Keep-Alive) + + httplib::Headers default_headers; ///< 默认头部,随所有请求发送 + }; + + /** + * @class NetRequest + * @brief HTTP 客户端封装(基于 cpp-httplib) + * + * 提供同步和异步的 GET/POST 请求、并发限制、可选缓存、日志回调、 + * 性能统计以及断点续传下载到文件等能力。 + */ + class NetRequest + { + public: + using LogCallback = std::function; ///< 日志回调类型 + + /** + * @brief 运行时统计数据 + */ + struct Stats + { + uint64_t total_requests = 0; ///< 累计请求次数 + uint64_t total_errors = 0; ///< 累计失败次数 + double last_latency_ms = 0.0;///< 最近一次请求耗时(毫秒) + double avg_latency_ms = 0.0; ///< 指数平滑后的平均耗时(毫秒) + }; + + /** + * @brief 构造函数 + * @param options 请求参数配置(目标地址、超时、默认头部等) + */ + explicit NetRequest(const RequestOptions &options); + /** + * @brief 析构函数 + */ + ~NetRequest(); + + /** + * @brief 设置日志回调 + * @param logger 回调函数,接受一行日志字符串 + */ + void setLogger(LogCallback logger); + /** + * @brief 设置最大并发请求数 + * @param n 并发上限(最小值为 1) + */ + void setMaxConcurrentRequests(size_t n); + /** + * @brief 启用内存缓存 + * @param ttl 缓存有效期(超时后自动失效) + */ + void enableCache(std::chrono::milliseconds ttl); + /** + * @brief 禁用并清空缓存 + */ + void disableCache(); + + /** + * @brief 发送同步 GET 请求 + * @param path 资源路径(会与 base_path 合并) + * @param query 查询参数(会拼接为 ?k=v&...) + * @param headers 额外请求头(与默认头部合并) + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional Get(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST JSON 请求 + * @param path 资源路径 + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST 表单请求(application/x-www-form-urlencoded) + * @param path 资源路径 + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 GET 请求 + * @return std::future,用于获取响应结果 + */ + std::future> GetAsync(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST JSON 请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST 表单请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 下载文件到本地,支持断点续传 + * @param path 资源路径 + * @param local_file 本地保存路径 + * @param headers 额外头部 + * @param resume 是否启用续传(Range) + * @param chunk_size 预留参数:分块大小(当前由 httplib 内部回调驱动) + * @param err 可选错误码输出 + * @return true 下载成功(200 或 206),false 失败 + */ + bool DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers = {}, + bool resume = true, + size_t chunk_size = 1 << 15, + ErrorCode *err = nullptr); + + /** + * @brief 获取统计数据快照 + */ + Stats getStats() const; + + /** + * @brief 便捷:直接用完整 URL 发起 GET(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickGet(const std::string &url, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST JSON(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST 表单(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + private: + struct Impl; + Impl *impl_; + }; +} \ No newline at end of file diff --git a/Identification/NetraLib/include/Netra.hpp b/Identification/NetraLib/include/Netra.hpp new file mode 100644 index 0000000..362ae28 --- /dev/null +++ b/Identification/NetraLib/include/Netra.hpp @@ -0,0 +1,426 @@ +#pragma once +#include "QCL_Include.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @class TcpServer + * @brief 简单的多线程TCP服务器类,支持多个客户端连接,数据收发及断开处理 + * + * 该类使用一个线程专门用于监听客户端连接, + * 每当有客户端连接成功时,为其创建一个独立线程处理该客户端的数据收发。 + * 线程安全地管理所有客户端Socket句柄。 + */ + class TcpServer + { + public: + /** + * @brief 构造函数,指定监听端口 + * @param port 服务器监听端口号 + */ + TcpServer(int port); + + /** + * @brief 析构函数,自动调用 stop() 停止服务器并清理资源 + */ + ~TcpServer(); + + /** + * @brief 启动服务器,创建监听socket,开启监听线程 + * @return 启动成功返回true,失败返回false + */ + bool start(); + + /** + * @brief 停止服务器,关闭所有连接,释放资源,等待所有线程退出 + */ + void stop(); + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端Socket描述符 + * @param message 发送的字符串消息 + */ + void sendToClient(int clientSock, const std::string &message); + + /** + * @brief 从指定客户端接收数据(单次调用) + * @param clientSock 客户端Socket描述符 + * @param flag:false 非阻塞模式,true 阻塞模式 + */ + std::string receiveFromClient(int clientSock, bool flag = true); + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *getClientIPAndPort(int clientSock); + + /** + * @brief 获取当前所有已连接客户端Socket的副本 + * @return 包含所有客户端Socket的vector,线程安全 + */ + std::vector getClientSockets(); + + /** + * @brief 从服务器的客户端列表中移除并关闭一个客户端socket + * @param clientSock 客户端Socket描述符 + */ + void removeClient(int clientSock); + + /** + * @brief 非阻塞探测客户端是否已断开(不消耗数据) + * @param clientSock 客户端Socket描述符 + * @return true 已断开或发生致命错误;false 仍然存活或暂无数据 + */ + bool isClientDisconnected(int clientSock); + + private: + /** + * @brief 监听并接受新的客户端连接(运行在独立线程中) + */ + void acceptClients(); + + private: + int serverSock_; ///< 服务器监听Socket描述符 + int port_; ///< 服务器监听端口 + std::atomic running_; ///< 服务器运行状态标志(线程安全) + std::vector clientThreads_; ///< 用于处理每个客户端的线程集合 + std::thread acceptThread_; ///< 负责监听新连接的线程 + std::mutex clientsMutex_; ///< 保护clientSockets_的互斥锁 + std::vector clientSockets_; ///< 当前所有连接的客户端Socket集合 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief 文件写入工具类(线程安全) + * + * 该类支持多种文件写入方式: + * - 覆盖写文本 + * - 追加写文本 + * - 按位置覆盖原文写入 + * - 二进制覆盖写 + * - 二进制追加写 + * + * 特点: + * - 文件不存在时自动创建 + * - 支持覆盖和追加两种模式 + * - 支持二进制模式,适合写入非文本数据 + * - 内部使用 std::mutex 实现线程安全 + */ + class WriteFile + { + public: + /** + * @brief 构造函数 + * @param filePath 文件路径 + */ + explicit WriteFile(const std::string &filePath); + + /** + * @brief 覆盖写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteText(const std::string &content); + + /** + * @brief 追加写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendText(const std::string &content); + + /** + * @brief 覆盖写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteBinary(const std::vector &data); + + /** + * @brief 追加写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendBinary(const std::vector &data); + + /** + * @brief 计算第一个指定字节序列前的字节数 + * @param pattern 要查找的字节序列 + * @param includePattern true 表示返回值包含 pattern 自身长度,false 表示不包含 + * @return size_t 字节数,如果文件没打开或 pattern 为空则返回 0 + */ + size_t countBytesPattern(const std::string &pattern, bool includePattern = false); + + /** + * @brief 在文件中查找指定字节序列并在其后写入内容,如果不存在则追加到文件末尾 + * @param pattern 要查找的字节序列 + * @param content 要写入的内容 + * @return true 写入成功,false 文件打开失败 + * + * 功能说明: + * 1. 若文件中存在 pattern,则删除 pattern 之后的所有内容,并在其后插入 content。 + * 2. 若文件中不存在 pattern,则在文件末尾追加 content,若末尾无换行符则先补充换行。 + */ + bool writeAfterPatternOrAppend(const std::string &pattern, const std::string &content); + + /** + * @brief 在文件指定位置之后插入内容 + * @param content 要插入的内容 + * @param pos 插入位置(从文件开头算起的字节偏移量) + * @param length 插入的长度(>= content.size() 时,多余部分用空字节填充;< content.size() 时只截取前 length 个字节) + * @return true 插入成功,false 文件打开失败或参数不合法 + * + * 功能说明: + * 1. 不会覆盖原有数据,而是将 pos 之后的内容整体向后移动 length 个字节。 + * 2. 如果 length > content.size(),则在 content 后补充 '\0'(或空格,可按需求改)。 + * 3. 如果 length < content.size(),则只写入 content 的前 length 个字节。 + * 4. 文件整体大小会增加 length 个字节。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * insertAfterPos("XY", 2, 3) // 在索引 2 后插入 + * 结果: "ABX Y\0CDEFG" (这里 \0 代表补充的空字节) + */ + bool insertAfterPos(const std::string &content, size_t pos, size_t length); + + /** + * @brief 在文件指定位置覆盖写入内容 + * @param content 要写入的内容 + * @param pos 覆盖起始位置(从文件开头算起的字节偏移量) + * @param length 覆盖长度 + * @return true 覆盖成功,false 文件打开失败或 pos 越界 + * + * 功能说明: + * 1. 从 pos 开始覆盖 length 个字节,不会移动或增加文件大小。 + * 2. 如果 content.size() >= length,则只写入前 length 个字节。 + * 3. 如果 content.size() < length,则写入 content,并用 '\0' 补齐至 length。 + * 4. 如果 pos + length 超过文件末尾,则只覆盖到文件尾部,不会越界。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * overwriteAtPos("XY", 2, 3) + * 结果: "ABXYEFG" (原 "CDE" 被 "XY\0" 覆盖,\0 实际不可见) + */ + bool overwriteAtPos(const std::string &content, size_t pos, size_t length); + + void close(); + + private: + std::string filePath_; ///< 文件路径 + std::mutex writeMutex_; ///< 线程锁,保证多线程写入安全 + + /** + * @brief 通用文本写入接口(线程安全) + * @param content 要写入的内容 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeToFile(const std::string &content, std::ios::openmode mode); + + /** + * @brief 通用二进制写入接口(线程安全) + * @param data 要写入的二进制数据 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeBinary(const std::vector &data, std::ios::openmode mode); + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief ReadFile 类 - 读文件操作工具类 + * + * 功能: + * 1. 读取全文(文本 / 二进制) + * 2. 按行读取 + * 3. 按字节数读取 + * 4. 获取指定字节序列前的字节数(包含该字节序列) + * 5. 检查文件是否存在 + * 6. 获取文件大小 + * + * 设计: + * - 与 WriteFile 类风格保持一致 + * - 支持文本文件与二进制文件 + * - 自动关闭文件(析构时) + * - 内部使用 std::mutex 实现线程安全 + */ + class ReadFile + { + public: + /** + * @brief 构造函数 + * @param filename 文件路径 + */ + explicit ReadFile(const std::string &filename); + + /** + * @brief 析构函数,自动关闭文件 + */ + ~ReadFile(); + + /** + * @brief 打开文件(以二进制方式) + * @return true 打开成功 + * @return false 打开失败 + */ + bool Open(); + + /** + * @brief 关闭文件 + */ + void Close(); + + /** + * @brief 文件是否已经打开 + */ + bool IsOpen() const; + + /** + * @brief 读取全文(文本模式) + * @return 文件内容字符串 + */ + std::string ReadAllText(); + + /** + * @brief 读取全文(二进制模式) + * @return 文件内容字节数组 + */ + std::vector ReadAllBinary(); + + /** + * @brief 按行读取文本 + * @return 每行作为一个字符串的 vector + */ + std::vector ReadLines(); + + /** + * @brief 读取指定字节数 + * @param count 要读取的字节数 + * @return 实际读取到的字节数据 + */ + std::vector ReadBytes(size_t count); + + /** + * @brief 获取指定字节序列前的字节数(包含该字节序列) + * @param marker 要查找的字节序列(可能不止一个字节) + * @return 如果找到,返回前面部分字节数;找不到返回0 + */ + size_t GetBytesBefore(const std::string &marker, bool includeMarker = false); + + /** + * @brief 从指定位置读取指定字节数,默认读取到文件末尾 + * @param pos 起始位置(字节偏移) + * @param count 要读取的字节数,默认为0表示读取到文件末尾 + * @return 读取到的字节数据 + */ + std::vector ReadBytesFrom(size_t pos, size_t count = 0); + + /** + * @brief 检查文件是否存在 + */ + bool FileExists() const; + + /** + * @brief 获取文件大小(字节数) + */ + size_t GetFileSize() const; + + /** + * @brief 重置读取位置到文件开头 + */ + void Reset(); + + private: + std::string filename_; // 文件路径 + std::ifstream file_; // 文件流对象 + mutable std::mutex mtx_; // 可变,保证 const 方法也能加锁 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals(); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 字符串操作 + // 去除字符串的左空格 + std::string Ltrim(const std::string &s); + + // 去除字符串右侧的空格 + std::string Rtrim(const std::string &s); + + // 去除字符串左右两侧的空格 + std::string LRtrim(const std::string &s); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // c++进行格式化输出 + // 通用类型转字符串 + template + std::string to_string_any(const T &value) + { + std::ostringstream oss; + oss << value; + return oss.str(); + } + + // 递归获取 tuple 中 index 对应参数 + template + std::string get_tuple_arg(const Tuple &tup, std::size_t index) + { + if constexpr (I < std::tuple_size_v) + { + if (I == index) + return to_string_any(std::get(tup)); + else + return get_tuple_arg(tup, index); + } + else + { + throw std::runtime_error("Too few arguments for format string"); + } + } + + // format 函数 + template + std::string format(const std::string &fmt, const Args &...args) + { + std::ostringstream oss; + std::tuple tup(args...); + size_t pos = 0; + size_t arg_idx = 0; + + while (pos < fmt.size()) + { + if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '{') + { + oss << '{'; + pos += 2; + } + else if (fmt[pos] == '}' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << '}'; + pos += 2; + } + else if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << get_tuple_arg(tup, arg_idx++); + pos += 2; + } + else + { + oss << fmt[pos++]; + } + } + + if (arg_idx < sizeof...(Args)) + throw std::runtime_error("Too many arguments for format string"); + + return oss.str(); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +} diff --git a/Identification/NetraLib/include/QCL_Include.hpp b/Identification/NetraLib/include/QCL_Include.hpp new file mode 100644 index 0000000..52a20fb --- /dev/null +++ b/Identification/NetraLib/include/QCL_Include.hpp @@ -0,0 +1,55 @@ +/** + * @file qcl_include.hpp + * @brief 通用C++开发头文件集合(Linux环境) + * @note 使用前确保目标平台支持C++17标准 + */ +#ifndef QCL_INCLUDE_HPP +#define QCL_INCLUDE_HPP + +// ==================== C/C++基础运行时库 ==================== +#include // 标准输入输出流(cin/cout/cerr) +#include // std::string类及相关操作 +#include // C风格字符串操作(strcpy/strcmp等) +#include // 通用工具函数(atoi/rand/malloc等) +#include // C风格IO(printf/scanf) +#include // 断言宏(调试期检查) +#include // 数学函数(sin/pow等) +#include // 时间处理(time/clock) +#include // 信号处理(signal/kill) +#include // 智能指针 + +// ==================== STL容器与算法 ==================== +#include // 动态数组(连续内存容器) +#include // 双向链表 +#include // 双端队列 +#include // 有序键值对(红黑树实现) +#include // 有序集合 +#include // 哈希表实现的键值对 +#include // 哈希表实现的集合 +#include // 栈适配器(LIFO) +#include // 队列适配器(FIFO) +#include // 通用算法(sort/find等) +#include // 数值算法(accumulate等) +#include // 迭代器相关 + +// ==================== 字符串与流处理 ==================== +#include // 字符串流(内存IO) +#include // 文件流(文件IO) +#include // 流格式控制(setw/setprecision) +#include // 正则表达式 +#include // 文件系统(C++17) +#include + +// ==================== 并发编程支持 ==================== +#include // 线程管理(std::thread) +#include // 互斥锁(mutex/lock_guard) +#include // 原子操作(线程安全变量) +#include // 条件变量(线程同步) + +// ==================== Linux网络编程 ==================== +#include // 套接字基础API(socket/bind) +#include // IPV4/IPV6地址结构体 +#include // 地址转换函数(inet_pton等) +#include // POSIX API(close/read/write) + +#endif // QCL_INCLUDE_HPP diff --git a/Identification/NetraLib/include/README.md b/Identification/NetraLib/include/README.md new file mode 100644 index 0000000..984ab89 --- /dev/null +++ b/Identification/NetraLib/include/README.md @@ -0,0 +1,850 @@ +cpp-httplib +=========== + +[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions) + +A C++11 single-file header-only cross platform HTTP/HTTPS library. + +It's extremely easy to setup. Just include the **httplib.h** file in your code! + +NOTE: This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. + +Simple examples +--------------- + +#### Server (Multi-threaded) + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Server svr; + +// HTTPS +httplib::SSLServer svr; + +svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { + res.set_content("Hello World!", "text/plain"); +}); + +svr.listen("0.0.0.0", 8080); +``` + +#### Client + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co"); + +// HTTPS +httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co"); + +auto res = cli.Get("/hi"); +res->status; +res->body; +``` + +SSL Support +----------- + +SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. + +NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0. + +NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`. + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// Server +httplib::SSLServer svr("./cert.pem", "./key.pem"); + +// Client +httplib::Client cli("https://localhost:1234"); // scheme + host +httplib::SSLClient cli("localhost:1234"); // host +httplib::SSLClient cli("localhost", 1234); // host, port + +// Use your CA bundle +cli.set_ca_cert_path("./ca-bundle.crt"); + +// Disable cert verification +cli.enable_server_certificate_verification(false); +``` + +NOTE: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. + +Server +------ + +```c++ +#include + +int main(void) +{ + using namespace httplib; + + Server svr; + + svr.Get("/hi", [](const Request& req, Response& res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { + auto numbers = req.matches[1]; + res.set_content(numbers, "text/plain"); + }); + + svr.Get("/body-header-param", [](const Request& req, Response& res) { + if (req.has_header("Content-Length")) { + auto val = req.get_header_value("Content-Length"); + } + if (req.has_param("key")) { + auto val = req.get_param_value("key"); + } + res.set_content(req.body, "text/plain"); + }); + + svr.Get("/stop", [&](const Request& req, Response& res) { + svr.stop(); + }); + + svr.listen("localhost", 1234); +} +``` + +`Post`, `Put`, `Delete` and `Options` methods are also supported. + +### Bind a socket to multiple interfaces and any available port + +```cpp +int port = svr.bind_to_any_port("0.0.0.0"); +svr.listen_after_bind(); +``` + +### Static File Server + +```cpp +// Mount / to ./www directory +auto ret = svr.set_mount_point("/", "./www"); +if (!ret) { + // The specified base directory doesn't exist... +} + +// Mount /public to ./www directory +ret = svr.set_mount_point("/public", "./www"); + +// Mount /public to ./www1 and ./www2 directories +ret = svr.set_mount_point("/public", "./www1"); // 1st order to search +ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search + +// Remove mount / +ret = svr.remove_mount_point("/"); + +// Remove mount /public +ret = svr.remove_mount_point("/public"); +``` + +```cpp +// User defined file extension and MIME type mappings +svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); +``` + +The followings are built-in mappings: + +| Extension | MIME Type | Extension | MIME Type | +| :--------- | :-------------------------- | :--------- | :-------------------------- | +| css | text/css | mpga | audio/mpeg | +| csv | text/csv | weba | audio/webm | +| txt | text/plain | wav | audio/wave | +| vtt | text/vtt | otf | font/otf | +| html, htm | text/html | ttf | font/ttf | +| apng | image/apng | woff | font/woff | +| avif | image/avif | woff2 | font/woff2 | +| bmp | image/bmp | 7z | application/x-7z-compressed | +| gif | image/gif | atom | application/atom+xml | +| png | image/png | pdf | application/pdf | +| svg | image/svg+xml | mjs, js | application/javascript | +| webp | image/webp | json | application/json | +| ico | image/x-icon | rss | application/rss+xml | +| tif | image/tiff | tar | application/x-tar | +| tiff | image/tiff | xhtml, xht | application/xhtml+xml | +| jpeg, jpg | image/jpeg | xslt | application/xslt+xml | +| mp4 | video/mp4 | xml | application/xml | +| mpeg | video/mpeg | gz | application/gzip | +| webm | video/webm | zip | application/zip | +| mp3 | audio/mp3 | wasm | application/wasm | + +### File request handler + +```cpp +// The handler is called right before the response is sent to a client +svr.set_file_request_handler([](const Request &req, Response &res) { + ... +}); +``` + +NOTE: These static file server methods are not thread-safe. + +### Logging + +```cpp +svr.set_logger([](const auto& req, const auto& res) { + your_logger(req, res); +}); +``` + +### Error handler + +```cpp +svr.set_error_handler([](const auto& req, auto& res) { + auto fmt = "

Error Status: %d

"; + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), fmt, res.status); + res.set_content(buf, "text/html"); +}); +``` + +### Exception handler +The exception handler gets called if a user routing handler throws an error. + +```cpp +svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { + auto fmt = "

Error 500

%s

"; + char buf[BUFSIZ]; + try { + std::rethrow_exception(ep); + } catch (std::exception &e) { + snprintf(buf, sizeof(buf), fmt, e.what()); + } catch (...) { // See the following NOTE + snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); + } + res.set_content(buf, "text/html"); + res.status = 500; +}); +``` + +NOTE: if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful! + +### Pre routing handler + +```cpp +svr.set_pre_routing_handler([](const auto& req, auto& res) { + if (req.path == "/hello") { + res.set_content("world", "text/html"); + return Server::HandlerResponse::Handled; + } + return Server::HandlerResponse::Unhandled; +}); +``` + +### Post routing handler + +```cpp +svr.set_post_routing_handler([](const auto& req, auto& res) { + res.set_header("ADDITIONAL_HEADER", "value"); +}); +``` + +### 'multipart/form-data' POST data + +```cpp +svr.Post("/multipart", [&](const auto& req, auto& res) { + auto size = req.files.size(); + auto ret = req.has_file("name1"); + const auto& file = req.get_file_value("name1"); + // file.filename; + // file.content_type; + // file.content; +}); +``` + +### Receive content with a content receiver + +```cpp +svr.Post("/content_receiver", + [&](const Request &req, Response &res, const ContentReader &content_reader) { + if (req.is_multipart_form_data()) { + // NOTE: `content_reader` is blocking until every form data field is read + MultipartFormDataItems files; + content_reader( + [&](const MultipartFormData &file) { + files.push_back(file); + return true; + }, + [&](const char *data, size_t data_length) { + files.back().content.append(data, data_length); + return true; + }); + } else { + std::string body; + content_reader([&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); + } + }); +``` + +### Send content with the content provider + +```cpp +const size_t DATA_CHUNK_SIZE = 4; + +svr.Get("/stream", [&](const Request &req, Response &res) { + auto data = new std::string("abcdefg"); + + res.set_content_provider( + data->size(), // Content length + "text/plain", // Content type + [data](size_t offset, size_t length, DataSink &sink) { + const auto &d = *data; + sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE)); + return true; // return 'false' if you want to cancel the process. + }, + [data](bool success) { delete data; }); +}); +``` + +Without content length: + +```cpp +svr.Get("/stream", [&](const Request &req, Response &res) { + res.set_content_provider( + "text/plain", // Content type + [&](size_t offset, DataSink &sink) { + if (/* there is still data */) { + std::vector data; + // prepare data... + sink.write(data.data(), data.size()); + } else { + sink.done(); // No more data + } + return true; // return 'false' if you want to cancel the process. + }); +}); +``` + +### Chunked transfer encoding + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done(); // No more data + return true; // return 'false' if you want to cancel the process. + } + ); +}); +``` + +With trailer: + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_header("Trailer", "Dummy1, Dummy2"); + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done_with_trailer({ + {"Dummy1", "DummyVal1"}, + {"Dummy2", "DummyVal2"} + }); + return true; + } + ); +}); +``` + +### 'Expect: 100-continue' handler + +By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header. + +```cpp +// Send a '417 Expectation Failed' response. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return 417; +}); +``` + +```cpp +// Send a final status without reading the message body. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return res.status = 401; +}); +``` + +### Keep-Alive connection + +```cpp +svr.set_keep_alive_max_count(2); // Default is 5 +svr.set_keep_alive_timeout(10); // Default is 5 +``` + +### Timeout + +```c++ +svr.set_read_timeout(5, 0); // 5 seconds +svr.set_write_timeout(5, 0); // 5 seconds +svr.set_idle_interval(0, 100000); // 100 milliseconds +``` + +### Set maximum payload length for reading a request body + +```c++ +svr.set_payload_max_length(1024 * 1024 * 512); // 512MB +``` + +### Server-Sent Events + +Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc). + +### Default thread pool support + +`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. + +If you want to set the thread count at runtime, there is no convenient way... But here is how. + +```cpp +svr.new_task_queue = [] { return new ThreadPool(12); }; +``` + +### Override the default thread pool with yours + +You can supply your own thread pool implementation according to your need. + +```cpp +class YourThreadPoolTaskQueue : public TaskQueue { +public: + YourThreadPoolTaskQueue(size_t n) { + pool_.start_with_thread_count(n); + } + + virtual void enqueue(std::function fn) override { + pool_.enqueue(fn); + } + + virtual void shutdown() override { + pool_.shutdown_gracefully(); + } + +private: + YourThreadPool pool_; +}; + +svr.new_task_queue = [] { + return new YourThreadPoolTaskQueue(12); +}; +``` + +Client +------ + +```c++ +#include +#include + +int main(void) +{ + httplib::Client cli("localhost", 1234); + + if (auto res = cli.Get("/hi")) { + if (res->status == 200) { + std::cout << res->body << std::endl; + } + } else { + auto err = res.error(); + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } +} +``` + +NOTE: Constructor with scheme-host-port string is now supported! + +```c++ +httplib::Client cli("localhost"); +httplib::Client cli("localhost:8080"); +httplib::Client cli("http://localhost"); +httplib::Client cli("http://localhost:8080"); +httplib::Client cli("https://localhost"); +httplib::SSLClient cli("localhost"); +``` + +### Error code + +Here is the list of errors from `Result::error()`. + +```c++ +enum Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, +}; +``` + +### GET with HTTP headers + +```c++ +httplib::Headers headers = { + { "Accept-Encoding", "gzip, deflate" } +}; +auto res = cli.Get("/hi", headers); +``` +or +```c++ +auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}}); +``` +or +```c++ +cli.set_default_headers({ + { "Accept-Encoding", "gzip, deflate" } +}); +auto res = cli.Get("/hi"); +``` + +### POST + +```c++ +res = cli.Post("/post", "text", "text/plain"); +res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded"); +``` + +### POST with parameters + +```c++ +httplib::Params params; +params.emplace("name", "john"); +params.emplace("note", "coder"); + +auto res = cli.Post("/post", params); +``` + or + +```c++ +httplib::Params params{ + { "name", "john" }, + { "note", "coder" } +}; + +auto res = cli.Post("/post", params); +``` + +### POST with Multipart Form Data + +```c++ +httplib::MultipartFormDataItems items = { + { "text1", "text default", "", "" }, + { "text2", "aωb", "", "" }, + { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, + { "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" }, + { "file3", "", "", "application/octet-stream" }, +}; + +auto res = cli.Post("/multipart", items); +``` + +### PUT + +```c++ +res = cli.Put("/resource/foo", "text", "text/plain"); +``` + +### DELETE + +```c++ +res = cli.Delete("/resource/foo"); +``` + +### OPTIONS + +```c++ +res = cli.Options("*"); +res = cli.Options("/resource/foo"); +``` + +### Timeout + +```c++ +cli.set_connection_timeout(0, 300000); // 300 milliseconds +cli.set_read_timeout(5, 0); // 5 seconds +cli.set_write_timeout(5, 0); // 5 seconds +``` + +### Receive content with a content receiver + +```c++ +std::string body; + +auto res = cli.Get("/large-data", + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); +``` + +```cpp +std::string body; + +auto res = cli.Get( + "/stream", Headers(), + [&](const Response &response) { + EXPECT_EQ(200, response.status); + return true; // return 'false' if you want to cancel the request. + }, + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; // return 'false' if you want to cancel the request. + }); +``` + +### Send content with a content provider + +```cpp +std::string body = ...; + +auto res = cli.Post( + "/stream", body.size(), + [](size_t offset, size_t length, DataSink &sink) { + sink.write(body.data() + offset, length); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### Chunked transfer encoding + +```cpp +auto res = cli.Post( + "/stream", + [](size_t offset, DataSink &sink) { + sink.os << "chunked data 1"; + sink.os << "chunked data 2"; + sink.os << "chunked data 3"; + sink.done(); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### With Progress Callback + +```cpp +httplib::Client client(url, port); + +// prints: 0 / 000 bytes => 50% complete +auto res = cli.Get("/", [](uint64_t len, uint64_t total) { + printf("%lld / %lld bytes => %d%% complete\n", + len, total, + (int)(len*100/total)); + return true; // return 'false' if you want to cancel the request. +} +); +``` + +![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) + +### Authentication + +```cpp +// Basic Authentication +cli.set_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_bearer_token_auth("token"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Proxy server support + +```cpp +cli.set_proxy("host", port); + +// Basic Authentication +cli.set_proxy_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_proxy_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_proxy_bearer_token_auth("pass"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Range + +```cpp +httplib::Client cli("httpbin.org"); + +auto res = cli.Get("/range/32", { + httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10' +}); +// res->status should be 206. +// res->body should be "bcdefghijk". +``` + +```cpp +httplib::make_range_header({{1, 10}, {20, -1}}) // 'Range: bytes=1-10, 20-' +httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599' +httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1' +``` + +### Keep-Alive connection + +```cpp +httplib::Client cli("localhost", 1234); + +cli.Get("/hello"); // with "Connection: close" + +cli.set_keep_alive(true); +cli.Get("/world"); + +cli.set_keep_alive(false); +cli.Get("/last-request"); // with "Connection: close" +``` + +### Redirect + +```cpp +httplib::Client cli("yahoo.com"); + +auto res = cli.Get("/"); +res->status; // 301 + +cli.set_follow_location(true); +res = cli.Get("/"); +res->status; // 200 +``` + +### Use a specific network interface + +NOTE: This feature is not available on Windows, yet. + +```cpp +cli.set_interface("eth0"); // Interface name, IP address or host name +``` + +Compression +----------- + +The server can apply compression to the following MIME type contents: + + * all text types except text/event-stream + * image/svg+xml + * application/javascript + * application/json + * application/xml + * application/xhtml+xml + +### Zlib Support + +'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked. + +### Brotli Support + +Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/google/brotli for more detail. + +### Compress request body on client + +```c++ +cli.set_compress(true); +res = cli.Post("/resource/foo", "...", "text/plain"); +``` + +### Compress response body on client + +```c++ +cli.set_decompress(false); +res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); +res->body; // Compressed data +``` + +Use `poll` instead of `select` +------------------------------ + +`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`. + + +Split httplib.h into .h and .cc +------------------------------- + +```console +$ ./split.py -h +usage: split.py [-h] [-e EXTENSION] [-o OUT] + +This script splits httplib.h into .h and .cc parts. + +optional arguments: + -h, --help show this help message and exit + -e EXTENSION, --extension EXTENSION + extension of the implementation file (default: cc) + -o OUT, --out OUT where to write the files (default: out) + +$ ./split.py +Wrote out/httplib.h and out/httplib.cc +``` + +NOTE +---- + +### g++ + +g++ 4.8 and below cannot build this library since `` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). + +### Windows + +Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand. + +```cpp +#include +#include +``` + +```cpp +#define WIN32_LEAN_AND_MEAN +#include +#include +``` + +NOTE: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. + +NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported. + +License +------- + +MIT license (© 2023 Yuji Hirose) + +Special Thanks To +----------------- + +[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy! diff --git a/Identification/NetraLib/include/encrypt.hpp b/Identification/NetraLib/include/encrypt.hpp new file mode 100644 index 0000000..42ea793 --- /dev/null +++ b/Identification/NetraLib/include/encrypt.hpp @@ -0,0 +1,14 @@ +#pragma once + +/* +主要是用于各种加密 +*/ + +#include "QCL_Include.hpp" + +using namespace std; + +namespace encrypt +{ + string MD5(const string &info); +} \ No newline at end of file diff --git a/Identification/NetraLib/include/httplib.h b/Identification/NetraLib/include/httplib.h new file mode 100644 index 0000000..1bdef69 --- /dev/null +++ b/Identification/NetraLib/include/httplib.h @@ -0,0 +1,8794 @@ +// +// httplib.h +// +// Copyright (c) 2023 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.12.2" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif // strcasecmp + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#ifndef _AIX +#include +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x1010100fL +#error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +struct ci { + bool operator()(const std::string &s1, const std::string &s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); + } +}; + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +} // namespace detail + +using Headers = std::multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + Params params; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + void set_redirect(const std::string &url, int status = 302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + template + ssize_t write_format(const char *fmt, const Args &...args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual void enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool : public TaskQueue { +public: + explicit ThreadPool(size_t n) : shutdown_(false) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + void enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = std::move(pool_.jobs_.front()); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_file_request_handler(Handler handler); + + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = std::vector>; + using HandlersForContentReader = + std::vector>; + + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, const Handlers &handlers); + bool + dispatch_request_for_content_reader(Request &req, Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers); + + bool parse_request_line(const char *s, Request &req); + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); + bool write_response(Stream &strm, bool close_connection, const Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + + virtual bool process_and_close_socket(socket_t sock); + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + + std::atomic is_running_{false}; + std::atomic done_{false}; + std::map file_extension_and_mimetype_map_; + Handler file_request_handler_; + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Logger logger_; + Expect100ContinueHandler expect_100_continue_handler_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(const Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; + template + T get_request_header_value(const std::string &key, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_; + Headers request_headers_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); + + void copy_settings(const ClientImpl &rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif + + Logger logger_; + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + Client(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +template +inline T get_header_value(const Headers & /*headers*/, + const std::string & /*key*/, size_t /*id*/ = 0, + uint64_t /*def*/ = 0) {} + +template <> +inline uint64_t get_header_value(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +template +inline T Request::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline T Response::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf{}; + + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +template +inline T Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, 0); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(Ranges ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void split(const char *b, const char *e, char d, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + virtual ~nocompressor() = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor(); + ~gzip_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + int v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + const char *charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = (code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + int val = 0; + int valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_file(const std::string &path) { +#ifdef _WIN32 + return _access_s(path.c_str(), 0) == 0; +#else + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +#endif +} + +inline bool is_dir(const std::string &path) { + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + int val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + int val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + size_t i = 0; + size_t beg = 0; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { break; } + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } +} + +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = false; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024 * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + +inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; + } + } +} + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET; + + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + std::copy(host.begin(), host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + + fcntl(sock, F_SETFD, FD_CLOEXEC); + if (socket_options) { socket_options(sock); } + + if (!bind_or_connect(sock, hints)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + if (sock == INVALID_SOCKET) { continue; } + +#ifndef _WIN32 + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + int yes = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), + sizeof(yes)); + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + int no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), + sizeof(no)); + } + + // bind or connect + if (bind_or_connect(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + freeaddrinfo(result); + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + freeifaddrs(ifap); + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + freeifaddrs(ifap); + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + freeifaddrs(ifap); + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if.c_str())) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { return false; } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline const char * +find_content_type(const std::string &path, + const std::map &user_data) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second.c_str(); } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return nullptr; + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline const char *status_message(int status) { + switch (status) { + case 100: return "Continue"; + case 101: return "Switching Protocol"; + case 102: return "Processing"; + case 103: return "Early Hints"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choice"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "unused"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Too Early"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + + default: + case 500: return "Internal Server Error"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + default: + return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + int ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + int ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + auto prev_avail_in = strm_.avail_in; + + ret = inflate(&strm_, Z_NO_FLUSH); + + if (prev_avail_in - strm_.avail_in == 0) { return false; } + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) return false; + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +inline bool compare_case_ignore(const std::string &a, const std::string &b) { + if (a.size() != b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + auto key = std::string(beg, key_end); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + fn(std::move(key), std::move(val)); + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + } else { + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; + } +#else + } else { + continue; // Skip invalid line. + } +#endif + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n < 0) { + return false; + } else if (n == 0) { + return true; + } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n")) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n")) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), + "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = 500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value(x.headers, "Content-Length"); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { status = exceed_payload_max_length ? 413 : 400; } + return ret; + }); +} // namespace detail + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + res.location = location; + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = content_type.substr(beg, end - beg); + if (boundary.length() >= 2 && boundary.front() == '"' && + boundary.back() == '"') { + boundary = boundary.substr(1, boundary.size() - 2); + } + return !boundary.empty(); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); + std::smatch m; + if (std::regex_match(s, m, re_first_range)) { + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); + bool all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) return; + static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); + std::cmatch cm; + if (std::regex_match(b, e, cm, re_another_range)) { + ssize_t first = -1; + if (!cm.str(1).empty()) { + first = static_cast(std::stoll(cm.str(1))); + } + + ssize_t last = -1; + if (!cm.str(2).empty()) { + last = static_cast(std::stoll(cm.str(2))); + } + + if (first != -1 && last != -1 && first > last) { + all_valid_ranges = false; + return; + } + ranges.emplace_back(std::make_pair(first, last)); + } + }); + return all_valid_ranges; + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + // TODO: support 'filename*' + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~", + std::regex_constants::icase); + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + static const std::string header_name = "content-type:"; + const auto header = buf_head(pos); + if (start_with_case_ignore(header, header_name)) { + file_.content_type = trim_copy(header.substr(header_name.size())); + } else { + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + file_.name = m[1]; + file_.filename = m[2]; + } else { + is_valid_ = false; + return false; + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_crlf_.size() > buf_size()) { return true; } + if (buf_start_with(dash_crlf_)) { + buf_erase(dash_crlf_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + const std::string dash_crlf_ = "--\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string to_lower(const char *beg, const char *end) { + std::string out; + auto it = beg; + while (it != end) { + out += static_cast(::tolower(*it)); + it++; + } + return out; +} + +inline std::string make_multipart_data_boundary() { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); + + std::string result = "--cpp-httplib-multipart-data-"; + + for (auto i = 0; i < 16; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + + return result; +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) body += serialize_multipart_formdata_finish(boundary); + + return body; +} + +inline std::pair +get_range_offset_and_length(const Request &req, size_t content_length, + size_t index) { + auto r = req.ranges[index]; + + if (r.first == -1 && r.second == -1) { + return std::make_pair(0, content_length); + } + + auto slen = static_cast(content_length); + + if (r.first == -1) { + r.first = (std::max)(static_cast(0), slen - r.second); + r.second = slen - 1; + } + + if (r.second == -1) { r.second = slen - 1; } + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field(size_t offset, size_t length, + size_t content_length) { + std::string field = "bytes "; + field += std::to_string(offset); + field += "-"; + field += std::to_string(offset + length - 1); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + SToken stoken, CToken ctoken, + Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offsets = get_range_offset_and_length(req, res.body.size(), i); + auto offset = offsets.first; + auto length = offsets.second; + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset, length, res.body.size())); + ctoken("\r\n"); + ctoken("\r\n"); + if (!content(offset, length)) { return false; } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--\r\n"); + + return true; +} + +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; + }); +} + +inline size_t +get_multipart_ranges_data_length(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool write_multipart_ranges_data(Stream &strm, const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + const T &is_shutting_down) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline std::pair +get_range_offset_and_length(const Request &req, const Response &res, + size_t index) { + auto r = req.ranges[index]; + + if (r.second == -1) { + r.second = static_cast(res.content_length_) - 1; + } + + return std::make_pair(r.first, r.second - r.first + 1); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << (unsigned int)hash[i]; + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (int i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + auto m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 +inline std::string random_string(size_t length) { + auto randchar = []() -> char { + const char charset[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[static_cast(std::rand()) % max_index]; + }; + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + int dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } + + freeaddrinfo(result); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair make_range_header(Ranges ranges) { + std::string field = "bytes="; + auto i = 0; + for (auto r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const std::string &key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = 302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = true; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() {} + +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() {} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + if (detail::is_dir(dir)) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(HandlerWithResponse handler) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + if (bind_internal(host, port, socket_flags) < 0) return false; + return true; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + return bind_internal(host, 0, socket_flags); +} + +inline bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + auto se = detail::scope_exit([&]() { done_ = true; }); + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } +} + +inline bool Server::parse_request_line(const char *s, Request &req) { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + size_t count = 0; + + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), req.params); + } + break; + } + default: break; + } + count++; + }); + + if (count > 2) { return false; } + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); + } + + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + res.set_header("Content-Type", "text/plain"); + } + + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { + return false; + } + + if (!detail::write_headers(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + res.content_provider_success_ = false; + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = 413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); + if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } + res.status = req.has_header("Range") ? 206 : 200; + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + task_queue->enqueue([this, sock]() { process_and_close_socket(sock); }); + } + + task_queue->shutdown(); + } + + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + bool is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = 400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { + if (req.ranges.size() > 1) { + boundary = detail::make_multipart_data_boundary(); + + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + res.headers.emplace("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty()) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length(req, res, boundary, + content_type); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty()) { + ; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.body.size(), 0); + auto offset = offsets.first; + auto length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.body.size()); + res.set_header("Content-Range", content_range); + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } + } else { + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + Response res; + + res.version = "HTTP/1.1"; + + for (const auto &header : default_headers_) { + if (res.headers.find(header.first) == res.headers.end()) { + res.headers.insert(header); + } + } + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = 400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = 416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + auto status = 100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case 100: + case 417: + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + detail::status_message(status)); + break; + default: return write_response(strm, close_connection, req, res); + } + } + + // Rounting + bool routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + + if (routed) { + if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(host), port_(port), + host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) ip = it->second; + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == 100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == 407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.headers.emplace("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.headers.emplace("User-Agent", agent); + } +#endif + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.headers.emplace("Content-Length", length); + } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.headers.emplace("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.headers.emplace("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.headers.emplace("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + detail::write_headers(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.headers.emplace("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + ; + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by + // handle_request which is only called by send, which locks the request + // mutex during the process. It would be a bug to call it from a different + // thread since it's a thread-safety issue to do these things to the socket + // if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + size_t cur_item = 0, cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && items.size()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + bool has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + return false; + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, response_handler, + content_receiver, progress); +} + +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + int res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() {} + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + // add default password callback before opening encrypted private key + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata(ctx_, + (char *)private_key_password); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response res2; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, res2, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (res2.status == 407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res2, auth, true)) { + Response res3; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, res3, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } else { + res = res2; + return false; + } + } + + return true; +} + +inline bool SSLClient::load_certs() { + bool ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl2) { + SSL_set_tlsext_host_name(ssl2, host_.c_str()); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6; + struct in_addr addr; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); + auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(std::string(b, e)); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() {} + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); +} + +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline socket_t Client::socket() const { return cli_->socket(); } + +inline void Client::stop() { cli_->stop(); } + +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + +inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/Identification/NetraLib/src/NetRequest.cpp b/Identification/NetraLib/src/NetRequest.cpp new file mode 100644 index 0000000..f3aa541 --- /dev/null +++ b/Identification/NetraLib/src/NetRequest.cpp @@ -0,0 +1,635 @@ +#include "NetRequest.hpp" + +#include +#include +#include +#include +#include +#include + +namespace ntq +{ + namespace + { + static std::string joinPath(const std::string &base, const std::string &path) + { + if (base.empty()) return path.empty() || path[0] == '/' ? path : std::string("/") + path; + if (path.empty()) return base[0] == '/' ? base : std::string("/") + base; + bool base_has = base.front() == '/'; + bool base_end = base.back() == '/'; + bool path_has = path.front() == '/'; + std::string b = base_has ? base : std::string("/") + base; + if (base_end && path_has) return b + path.substr(1); + if (!base_end && !path_has) return b + "/" + path; + return b + path; + } + + static std::string paramsToQuery(const httplib::Params ¶ms) + { + if (params.empty()) return {}; + std::string s; + bool first = true; + for (auto &kv : params) + { + if (!first) s += '&'; + first = false; + s += kv.first; + s += '='; + s += kv.second; + } + return s; + } + + static httplib::Headers mergeHeaders(const httplib::Headers &a, const httplib::Headers &b) + { + httplib::Headers h = a; + for (auto &kv : b) + { + // 覆盖同名 header:先删再插 + h.erase(kv.first); + h.emplace(kv.first, kv.second); + } + return h; + } + } + + class ConcurrencyGate + { + public: + explicit ConcurrencyGate(size_t limit) : limit_(limit), active_(0) {} + + void set_limit(size_t limit) + { + std::lock_guard lk(mtx_); + limit_ = limit > 0 ? limit : 1; + cv_.notify_all(); + } + + struct Guard + { + ConcurrencyGate &g; + explicit Guard(ConcurrencyGate &gate) : g(gate) { g.enter(); } + ~Guard() { g.leave(); } + }; + + private: + friend struct Guard; + void enter() + { + std::unique_lock lk(mtx_); + cv_.wait(lk, [&]{ return active_ < limit_; }); + ++active_; + } + void leave() + { + std::lock_guard lk(mtx_); + if (active_ > 0) --active_; + cv_.notify_one(); + } + + size_t limit_; + size_t active_; + std::mutex mtx_; + std::condition_variable cv_; + }; + + struct NetRequest::Impl + { + RequestOptions opts; + LogCallback logger; + Stats stats; + + // 并发控制 + ConcurrencyGate gate{4}; + + // 缓存 + struct CacheEntry + { + HttpResponse resp; + std::chrono::steady_clock::time_point expiry; + }; + bool cache_enabled = false; + std::chrono::milliseconds cache_ttl{0}; + std::unordered_map cache; + std::mutex cache_mtx; + + void log(const std::string &msg) + { + if (logger) logger(msg); + } + + template + void apply_client_options(ClientT &cli) + { + const time_t c_sec = static_cast(opts.connect_timeout_ms / 1000); + const time_t c_usec = static_cast((opts.connect_timeout_ms % 1000) * 1000); + const time_t r_sec = static_cast(opts.read_timeout_ms / 1000); + const time_t r_usec = static_cast((opts.read_timeout_ms % 1000) * 1000); + const time_t w_sec = static_cast(opts.write_timeout_ms / 1000); + const time_t w_usec = static_cast((opts.write_timeout_ms % 1000) * 1000); + cli.set_connection_timeout(c_sec, c_usec); + cli.set_read_timeout(r_sec, r_usec); + cli.set_write_timeout(w_sec, w_usec); + cli.set_keep_alive(opts.keep_alive); + } + + std::string build_full_path(const std::string &path) const + { + return joinPath(opts.base_path, path); + } + + std::string cache_key(const std::string &path, const httplib::Params ¶ms, const httplib::Headers &headers) + { + std::ostringstream oss; + oss << opts.scheme << "://" << opts.host << ':' << opts.port << build_full_path(path); + if (!params.empty()) oss << '?' << paramsToQuery(params); + for (auto &kv : headers) oss << '|' << kv.first << '=' << kv.second; + return oss.str(); + } + + void record_latency(double ms) + { + stats.last_latency_ms = ms; + const double alpha = 0.2; + if (stats.avg_latency_ms <= 0.0) stats.avg_latency_ms = ms; + else stats.avg_latency_ms = alpha * ms + (1.0 - alpha) * stats.avg_latency_ms; + } + + static ErrorCode map_error() + { + // 简化:无法区分具体错误码,统一归为 Network + return ErrorCode::Network; + } + }; + + NetRequest::NetRequest(const RequestOptions &options) + : impl_(new Impl) + { + impl_->opts = options; + if (impl_->opts.scheme == "https" && impl_->opts.port == 80) impl_->opts.port = 443; + if (impl_->opts.scheme == "http" && impl_->opts.port == 0) impl_->opts.port = 80; + } + + NetRequest::~NetRequest() + { + delete impl_; + } + + void NetRequest::setLogger(LogCallback logger) + { + impl_->logger = std::move(logger); + } + + void NetRequest::setMaxConcurrentRequests(size_t n) + { + impl_->gate.set_limit(n > 0 ? n : 1); + } + + void NetRequest::enableCache(std::chrono::milliseconds ttl) + { + impl_->cache_enabled = true; + impl_->cache_ttl = ttl.count() > 0 ? ttl : std::chrono::milliseconds(1000); + } + + void NetRequest::disableCache() + { + impl_->cache_enabled = false; + std::lock_guard lk(impl_->cache_mtx); + impl_->cache.clear(); + } + + ntq::optional NetRequest::Get(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, mergeHeaders(impl_->opts.default_headers, headers)); + std::lock_guard lk(impl_->cache_mtx); + auto it = impl_->cache.find(key); + if (it != impl_->cache.end() && std::chrono::steady_clock::now() < it->second.expiry) + { + if (err) *err = ErrorCode::None; + auto resp = it->second.resp; + resp.from_cache = true; + return resp; + } + } + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + impl_->log("HTTPS requested but OpenSSL is not enabled; falling back to error."); + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!result.has_value()) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, merged_headers); + std::lock_guard lk(impl_->cache_mtx); + impl_->cache[key] = Impl::CacheEntry{*result, std::chrono::steady_clock::now() + impl_->cache_ttl}; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + std::future> NetRequest::GetAsync(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, query, headers, err]() mutable { + ErrorCode local; + auto r = Get(path, query, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, json, headers, err]() mutable { + ErrorCode local; + auto r = PostJson(path, json, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, form, headers, err]() mutable { + ErrorCode local; + auto r = PostForm(path, form, headers, &local); + if (err) *err = local; + return r; + }); + } + + bool NetRequest::DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers, + bool resume, + size_t /*chunk_size*/, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + std::ios_base::openmode mode = std::ios::binary | std::ios::out; + size_t offset = 0; + if (resume) + { + std::ifstream in(local_file, std::ios::binary | std::ios::ate); + if (in) + { + offset = static_cast(in.tellg()); + } + mode |= std::ios::app; + } + else + { + mode |= std::ios::trunc; + } + + std::ofstream out(local_file, mode); + if (!out) + { + if (err) *err = ErrorCode::IOError; + impl_->stats.total_errors++; + return false; + } + + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + if (resume && offset > 0) + { + merged_headers.emplace("Range", "bytes=" + std::to_string(offset) + "-"); + } + + const auto full_path = impl_->build_full_path(path); + int status_code = 0; + ErrorCode local_err = ErrorCode::None; + + auto content_receiver = [&](const char *data, size_t data_length) { + out.write(data, static_cast(data_length)); + return static_cast(out); + }; + + bool ok = false; + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } + } + + out.close(); + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!ok) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return false; + } + if (err) *err = ErrorCode::None; + return true; + } + + NetRequest::Stats NetRequest::getStats() const + { + return impl_->stats; + } + + // ------------------------- Quick helpers ------------------------- + namespace { + struct ParsedURL { + std::string scheme; + std::string host; + int port = 0; + std::string path_and_query; + bool ok = false; + }; + + static ParsedURL parse_url(const std::string &url) + { + ParsedURL p; p.ok = false; + // very small parser: scheme://host[:port]/path[?query] + auto pos_scheme = url.find("://"); + if (pos_scheme == std::string::npos) return p; + p.scheme = url.substr(0, pos_scheme); + size_t pos_host = pos_scheme + 3; + + size_t pos_path = url.find('/', pos_host); + std::string hostport = pos_path == std::string::npos ? url.substr(pos_host) + : url.substr(pos_host, pos_path - pos_host); + auto pos_colon = hostport.find(':'); + if (pos_colon == std::string::npos) { + p.host = hostport; + p.port = (p.scheme == "https") ? 443 : 80; + } else { + p.host = hostport.substr(0, pos_colon); + std::string port_str = hostport.substr(pos_colon + 1); + p.port = port_str.empty() ? ((p.scheme == "https") ? 443 : 80) : std::atoi(port_str.c_str()); + } + p.path_and_query = (pos_path == std::string::npos) ? "/" : url.substr(pos_path); + p.ok = !p.host.empty(); + return p; + } + } + + ntq::optional NetRequest::QuickGet(const std::string &url, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.Get(p.path_and_query, {}, headers, err); + } + + ntq::optional NetRequest::QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostJson(p.path_and_query, json, headers, err); + } + + ntq::optional NetRequest::QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostForm(p.path_and_query, form, headers, err); + } +} diff --git a/Identification/NetraLib/src/Netra.cpp b/Identification/NetraLib/src/Netra.cpp new file mode 100644 index 0000000..a67282c --- /dev/null +++ b/Identification/NetraLib/src/Netra.cpp @@ -0,0 +1,693 @@ +#include "Netra.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TcpServer::TcpServer(int port) + : port_(port), running_(false), serverSock_(-1) {} + + /** + * @brief 析构函数中调用stop()确保服务器资源被释放 + */ + TcpServer::~TcpServer() + { + stop(); + } + + /** + * @brief 启动服务器: + * 1. 创建监听socket(TCP) + * 2. 绑定端口 + * 3. 监听端口 + * 4. 启动监听线程acceptThread_ + * + * @return 成功返回true,失败返回false + */ + bool TcpServer::start() + { + // 创建socket + serverSock_ = socket(AF_INET, SOCK_STREAM, 0); + if (serverSock_ < 0) + { + std::cerr << "Socket 创建失败\n"; + return false; + } + + // 设置socket地址结构 + sockaddr_in serverAddr; + std::memset(&serverAddr, 0, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port_); // 端口转网络字节序 + serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡IP + + // 允许端口重用,防止服务器异常关闭后端口被占用 + int opt = 1; + setsockopt(serverSock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + // 绑定端口 + if (bind(serverSock_, (sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) + { + std::cerr << "绑定失败\n"; + return false; + } + + // 开始监听,最大等待连接数为5 + if (listen(serverSock_, 5) < 0) + { + std::cerr << "监听失败\n"; + return false; + } + + // 设置运行标志为true + running_ = true; + + // 启动专门接受客户端连接的线程 + acceptThread_ = std::thread(&TcpServer::acceptClients, this); + + std::cout << "服务器启动,监听端口:" << port_ << std::endl; + return true; + } + + /** + * @brief 停止服务器: + * 1. 设置运行标志为false,通知线程退出 + * 2. 关闭监听socket + * 3. 关闭所有客户端socket,清理客户端列表 + * 4. 等待所有线程退出 + */ + void TcpServer::stop() + { + running_ = false; + + if (serverSock_ >= 0) + { + close(serverSock_); + serverSock_ = -1; + } + + { + // 线程安全关闭所有客户端socket + std::lock_guard lock(clientsMutex_); + for (int sock : clientSockets_) + { + close(sock); + } + clientSockets_.clear(); + } + + // 等待监听线程退出 + if (acceptThread_.joinable()) + acceptThread_.join(); + + // 等待所有客户端处理线程退出 + for (auto &t : clientThreads_) + { + if (t.joinable()) + t.join(); + } + + std::cout << "服务器已停止\n"; + } + + /** + * @brief acceptClients函数循环监听客户端连接请求 + * 每当accept成功: + * 1. 打印客户端IP和Socket信息 + * 2. 线程安全地将客户端Socket加入clientSockets_列表 + * 3. 创建新线程调用handleClient处理该客户端收发 + */ + void TcpServer::acceptClients() + { + while (running_) + { + sockaddr_in clientAddr; + socklen_t clientLen = sizeof(clientAddr); + int clientSock = accept(serverSock_, (sockaddr *)&clientAddr, &clientLen); + if (clientSock < 0) + { + if (running_) + std::cerr << "接受连接失败\n"; + continue; + } + + // 将客户端IP转换成字符串格式打印 + char clientIP[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(clientAddr.sin_addr), clientIP, INET_ADDRSTRLEN); + std::cout << "客户端连接,IP: " << clientIP << ", Socket: " << clientSock << std::endl; + + { + // 加锁保护共享的clientSockets_容器 + std::lock_guard lock(clientsMutex_); + clientSockets_.push_back(clientSock); + } + } + } + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端socket + * @param message 发送消息内容 + */ + void TcpServer::sendToClient(int clientSock, const std::string &message) + { + send(clientSock, message.c_str(), message.size(), 0); + } + + /** + * @brief 单次接收指定客户端数据 + * @param clientSock 客户端socket + */ + std::string TcpServer::receiveFromClient(int clientSock, bool flag) + { + char buffer[1024]; + std::memset(buffer, 0, sizeof(buffer)); + + int flags = flag ? 0 : MSG_DONTWAIT; + ssize_t bytesReceived = recv(clientSock, buffer, sizeof(buffer) - 1, flags); + + if (bytesReceived <= 0) + return {}; + + return std::string(buffer, bytesReceived); + } + + /** + * @brief 获取当前所有客户端Socket副本(线程安全) + * @return 包含所有客户端socket的vector副本 + */ + std::vector TcpServer::getClientSockets() + { + std::lock_guard lock(clientsMutex_); + return clientSockets_; + } + + void TcpServer::removeClient(int clientSock) + { + std::lock_guard lock(clientsMutex_); + for (auto it = clientSockets_.begin(); it != clientSockets_.end(); ++it) + { + if (*it == clientSock) + { + close(*it); + clientSockets_.erase(it); + break; + } + } + } + + bool TcpServer::isClientDisconnected(int clientSock) + { + char tmp; + ssize_t n = recv(clientSock, &tmp, 1, MSG_PEEK | MSG_DONTWAIT); + if (n == 0) + return true; // 对端有序关闭 + if (n < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return false; // 只是暂时无数据 + return true; // 其它错误视为断开 + } + return false; // 有数据可读 + } + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *TcpServer::getClientIPAndPort(int clientSock) + { + struct sockaddr_in addr; + socklen_t addr_size = sizeof(addr); + + // 获取客户端地址信息 + if (getpeername(clientSock, (struct sockaddr *)&addr, &addr_size) == -1) + { + perror("getpeername failed"); + return NULL; + } + + // 分配内存存储结果(格式: "IP:PORT") + char *result = (char *)malloc(INET_ADDRSTRLEN + 10); + if (!result) + return NULL; + + // 转换IP和端口 + char *ip = inet_ntoa(addr.sin_addr); + unsigned short port = ntohs(addr.sin_port); + + snprintf(result, INET_ADDRSTRLEN + 10, "%s:%d", ip, port); + return result; + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + WriteFile::WriteFile(const std::string &filePath) + : filePath_(filePath) {} + + /** + * @brief 覆盖写文本(线程安全) + */ + bool WriteFile::overwriteText(const std::string &content) + { + std::lock_guard lock(writeMutex_); // 加锁 + return writeToFile(content, std::ios::out | std::ios::trunc); + } + + /** + * @brief 追加写文本(线程安全) + */ + bool WriteFile::appendText(const std::string &content) + { + std::lock_guard lock(writeMutex_); + return writeToFile(content, std::ios::out | std::ios::app); + } + + /** + * @brief 覆盖写二进制(线程安全) + */ + bool WriteFile::overwriteBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::trunc | std::ios::binary); + } + + /** + * @brief 追加写二进制(线程安全) + */ + bool WriteFile::appendBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::app | std::ios::binary); + } + + /** + * @brief 通用文本写入(私有) + */ + bool WriteFile::writeToFile(const std::string &content, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file << content; + file.close(); + return true; + } + + /** + * @brief 通用二进制写入(私有) + */ + bool WriteFile::writeBinary(const std::vector &data, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file.write(data.data(), data.size()); + file.close(); + return true; + } + + size_t WriteFile::countBytesPattern(const std::string &pattern, bool includePattern) + { + std::lock_guard lock(writeMutex_); + + if (pattern.empty()) + return 0; + + std::ifstream file(filePath_, std::ios::binary); + if (!file.is_open()) + return 0; + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file.read(chunk, chunkSize) || file.gcount() > 0) + { + size_t bytesRead = file.gcount(); + buffer.append(chunk, bytesRead); + + size_t pos = buffer.find(pattern); + if (pos != std::string::npos) + { + size_t absolutePos = totalRead + pos; // 关键:加上 totalRead + return includePattern ? (absolutePos + pattern.size()) : absolutePos; + } + + if (buffer.size() > pattern.size()) + buffer.erase(0, buffer.size() - pattern.size()); + + totalRead += bytesRead; // 读完后再累计 + } + + return 0; + } + + bool WriteFile::writeAfterPatternOrAppend(const std::string &pattern, const std::string &content) + { + std::lock_guard lock(writeMutex_); + + // 读取整个文件 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + size_t pos = fileData.find(pattern); + if (pos != std::string::npos) + { + // 模式存在,插入位置在模式结尾 + pos += pattern.size(); + + // 删除模式后所有内容 + if (pos < fileData.size()) + fileData.erase(pos); + + // 插入新内容 + fileData.insert(pos, content); + } + else + { + // 模式不存在,直接追加到文件末尾 + if (!fileData.empty() && fileData.back() != '\n') + fileData += '\n'; // 保证换行 + fileData += content; + } + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::overwriteAtPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos >= fileData.size()) + return false; // pos 超过文件范围,无法覆盖 + + // 生成要覆盖的实际数据块 + std::string overwriteBlock; + if (content.size() >= length) + { + overwriteBlock = content.substr(0, length); + } + else + { + overwriteBlock = content; + overwriteBlock.append(length - content.size(), '\0'); // 补齐 + } + + // 计算实际可写范围 + size_t maxWritable = std::min(length, fileData.size() - pos); + + // 覆盖 + fileData.replace(pos, maxWritable, overwriteBlock.substr(0, maxWritable)); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::insertAfterPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos > fileData.size()) + pos = fileData.size(); // 如果 pos 超出范围,就视为文件末尾 + + // 生成要插入的实际数据块 + std::string insertBlock; + if (content.size() >= length) + { + insertBlock = content.substr(0, length); // 只取前 length 个字节 + } + else + { + insertBlock = content; // 全部内容 + insertBlock.append(length - content.size(), '\0'); // 补足空字节 + } + + // 插入到 pos 后面 + fileData.insert(pos + 1, insertBlock); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ReadFile::ReadFile(const std::string &filename) : filename_(filename) {} + + ReadFile::~ReadFile() + { + std::lock_guard lock(mtx_); + Close(); + } + + bool ReadFile::Open() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + file_.close(); + file_.open(filename_, std::ios::in | std::ios::binary); + return file_.is_open(); + } + + void ReadFile::Close() + { + if (file_.is_open()) + { + std::lock_guard lock(mtx_); + file_.close(); + } + } + + bool ReadFile::IsOpen() const + { + std::lock_guard lock(mtx_); + return file_.is_open(); + } + + std::string ReadFile::ReadAllText() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return ""; + + std::ostringstream ss; + ss << file_.rdbuf(); + return ss.str(); + } + + std::vector ReadFile::ReadAllBinary() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + return ReadBytes(GetFileSize()); + } + + std::vector ReadFile::ReadLines() + { + // std::lock_guard lock(mtx_); + // if (!file_.is_open() && !Open()) + // return {}; + + // std::vector lines; + // std::string line; + // while (std::getline(file_, line)) + // { + // lines.push_back(line); + // } + // return lines; + + // std::lock_guard lock(mtx_); + if (!file_.is_open()) { + file_.open(filename_, std::ios::in | std::ios::binary); + if (!file_.is_open()) return {}; + } + file_.clear(); + file_.seekg(0, std::ios::beg); + + std::vector lines; + std::string line; + while (std::getline(file_, line)) lines.push_back(line); + return lines; + } + + std::vector ReadFile::ReadBytes(size_t count) + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + std::vector buffer(count); + file_.read(buffer.data(), count); + buffer.resize(file_.gcount()); + return buffer; + } + + size_t ReadFile::GetBytesBefore(const std::string &marker, bool includeMarker) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return 0; + + file_.clear(); // 清除EOF和错误状态 + file_.seekg(0, std::ios::beg); // 回到文件开头 + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file_.read(chunk, chunkSize) || file_.gcount() > 0) + { + buffer.append(chunk, file_.gcount()); + size_t pos = buffer.find(marker); + if (pos != std::string::npos) + { + // 如果 includeMarker 为 true,返回包含 marker 的长度 + if (includeMarker) + return pos + marker.size(); + else + return pos; + } + + // 保留末尾部分,避免 buffer 无限增长 + if (buffer.size() > marker.size()) + buffer.erase(0, buffer.size() - marker.size()); + + totalRead += file_.gcount(); + } + + return 0; + } + + std::vector ReadFile::ReadBytesFrom(size_t pos, size_t count) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return {}; + + size_t filesize = GetFileSize(); + if (pos >= filesize) + return {}; // 起始位置超出文件大小 + + file_.clear(); // 清除 EOF 和错误状态 + file_.seekg(pos, std::ios::beg); + if (!file_) + return {}; + + size_t bytes_to_read = count; + if (count == 0 || pos + count > filesize) + bytes_to_read = filesize - pos; // 读取到文件末尾 + + std::vector buffer(bytes_to_read); + file_.read(buffer.data(), bytes_to_read); + + // 实际读取的字节数可能少于请求的数量 + buffer.resize(file_.gcount()); + + return buffer; + } + + bool ReadFile::FileExists() const + { + return std::filesystem::exists(filename_); + } + + size_t ReadFile::GetFileSize() const + { + if (!FileExists()) + return 0; + return std::filesystem::file_size(filename_); + } + + void ReadFile::Reset() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + { + file_.clear(); + file_.seekg(0, std::ios::beg); + } + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals() + { + // 忽略全部的信号 + for (int ii = 1; ii <= 64; ii++) + signal(ii, SIG_IGN); + } + + std::string Ltrim(const std::string &s) + { + size_t start = 0; + while (start < s.size() && std::isspace(static_cast(s[start]))) + { + ++start; + } + return s.substr(start); + } + + std::string Rtrim(const std::string &s) + { + if (s.empty()) + return s; + + size_t end = s.size(); + while (end > 0 && std::isspace(static_cast(s[end - 1]))) + { + --end; + } + return s.substr(0, end); + } + + std::string LRtrim(const std::string &s) + { + return Ltrim(Rtrim(s)); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} \ No newline at end of file diff --git a/Identification/NetraLib/src/encrypt.cpp b/Identification/NetraLib/src/encrypt.cpp new file mode 100644 index 0000000..a8aea93 --- /dev/null +++ b/Identification/NetraLib/src/encrypt.cpp @@ -0,0 +1,91 @@ +#include "encrypt.hpp" +#include + +namespace encrypt +{ + string MD5(const string &info) + { + auto leftrotate = [](uint32_t x, uint32_t c) -> uint32_t { return (x << c) | (x >> (32 - c)); }; + + static const uint32_t s[64] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + static const uint32_t K[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + uint32_t a0 = 0x67452301; + uint32_t b0 = 0xefcdab89; + uint32_t c0 = 0x98badcfe; + uint32_t d0 = 0x10325476; + + std::vector msg(info.begin(), info.end()); + uint64_t bit_len = static_cast(msg.size()) * 8ULL; + msg.push_back(0x80); + while ((msg.size() % 64) != 56) msg.push_back(0x00); + for (int i = 0; i < 8; ++i) msg.push_back(static_cast((bit_len >> (8 * i)) & 0xff)); + + for (size_t offset = 0; offset < msg.size(); offset += 64) + { + uint32_t M[16]; + for (int i = 0; i < 16; ++i) + { + size_t j = offset + i * 4; + M[i] = static_cast(msg[j]) | + (static_cast(msg[j + 1]) << 8) | + (static_cast(msg[j + 2]) << 16) | + (static_cast(msg[j + 3]) << 24); + } + + uint32_t A = a0, B = b0, C = c0, D = d0; + for (uint32_t i = 0; i < 64; ++i) + { + uint32_t F, g; + if (i < 16) { F = (B & C) | ((~B) & D); g = i; } + else if (i < 32) { F = (D & B) | ((~D) & C); g = (5 * i + 1) % 16; } + else if (i < 48) { F = B ^ C ^ D; g = (3 * i + 5) % 16; } + else { F = C ^ (B | (~D)); g = (7 * i) % 16; } + + F = F + A + K[i] + M[g]; + A = D; + D = C; + C = B; + B = B + leftrotate(F, s[i]); + } + + a0 += A; b0 += B; c0 += C; d0 += D; + } + + uint8_t digest[16]; + auto u32_to_le = [](uint32_t v, uint8_t out[4]) { + out[0] = static_cast(v & 0xff); + out[1] = static_cast((v >> 8) & 0xff); + out[2] = static_cast((v >> 16) & 0xff); + out[3] = static_cast((v >> 24) & 0xff); + }; + u32_to_le(a0, digest + 0); + u32_to_le(b0, digest + 4); + u32_to_le(c0, digest + 8); + u32_to_le(d0, digest + 12); + + static const char *hex = "0123456789abcdef"; + std::string out; + out.resize(32); + for (int i = 0; i < 16; ++i) + { + out[i * 2] = hex[(digest[i] >> 4) & 0x0f]; + out[i * 2 + 1] = hex[digest[i] & 0x0f]; + } + return out; + } +} \ No newline at end of file diff --git a/Identification/bin/init b/Identification/bin/init new file mode 100755 index 0000000..6746fd9 Binary files /dev/null and b/Identification/bin/init differ diff --git a/Identification/bin/nohup.out b/Identification/bin/nohup.out new file mode 100644 index 0000000..607ea58 --- /dev/null +++ b/Identification/bin/nohup.out @@ -0,0 +1,101 @@ +[sudo] password for orangepi: Config dir: /tmp/create_ap.wlan0.conf.vJUDtOgK +PID: 6646 +Network Manager found, set ap1 as unmanaged device... DONE +wlan0 is already associated with channel 40 (5200 MHz), fallback to channel 40 +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.oeTHnCi2 +PID: 7125 +Network Manager found, set ap0 as unmanaged device... DONE +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.aBjaWdxi +PID: 7443 +Network Manager found, set ap0 as unmanaged device... DONE +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.SejmPhA7 +PID: 7741 +Network Manager found, set ap0 as unmanaged device... DONE +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.WAvCwfWH +PID: 8154 +Network Manager found, set ap0 as unmanaged device... DONE +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.9YwXANE7 +PID: 9467 +Network Manager found, set ap0 as unmanaged device... DONE +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.2mm31O8b +PID: 9909 +Network Manager found, set ap0 as unmanaged device... DONE +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.VHZ6Igff +PID: 10247 +Network Manager found, set ap0 as unmanaged device... DONE +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.w3jurdOR +PID: 10603 +Network Manager found, set ap0 as unmanaged device... DONE +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.tgD5uXaf +PID: 10997 +Network Manager found, set ap0 as unmanaged device... DONE +Creating a virtual WiFi interface... command failed: No such device (-19) + +ERROR: Maybe your WiFi adapter does not fully support virtual interfaces. + Try again with --no-virt. + + +Doing cleanup.. done diff --git a/Identification/src/main.cpp b/Identification/src/main.cpp new file mode 100644 index 0000000..c392de1 --- /dev/null +++ b/Identification/src/main.cpp @@ -0,0 +1,430 @@ +/* +本程序用于 设备出厂获取 唯一标识符进行 wifi设置和修改 +向云端发送访问请求(ICCID号),并显示请求次数 +接收云端传传来的标识符,并保存至本地,根据表示符进行wifi设置 +第二次启动时,自动检测是否已进行初始化,若是,直接从配置文件中获取唯一标识符 +*/ +#include +#include +#include +#include //用于解析JSON字符串 +#include +#include +#include "NetRequest.hpp" +#include "Netra.hpp" +#include +#include + +using namespace std; +using namespace QCL; + +using namespace ntq; +using namespace chrono_literals; +namespace bp = boost::process; + +const string filePath = "/home/orangepi/RKApp/InitAuth/conf/.env"; +// 请求唯一标识符接口路径 +const string UUIDPost = "http://116.147.36.110:8095/device/requestQrcode"; + +const size_t MAXSIZE = 1024 * 1024; + +// 保存ICCID +string ICCID = ""; +string UUID; // 保存唯一标识 +int PostCount = 0; // 请求次数 + +mutex fileMutex; // 文件锁 +mutex logMutex; // 日志锁 +atomic g_Device_pid{0}; + +// 检测是否已进行初始化 +bool checkInit(); + +// 获取标识符并保存至配置文件中 +bool GetSignID(); + +// 设置wifi热点:名称,密码 +void setApNet(const string &name, const string &pwd); + +// 读取配置文件,并获取表示符 +void ReadUUID(); + +// 启动设备激活程序,等待用户激活 +void startActiveWare(); + +// 退出信号处理 +void forwardSigint(int); + +// 获取SIM卡号 +string GetSimICCID(const string &tty = "/dev/ttyUSB2"); + +// 清理日志 +void clearLog(); + +int main() +{ + // 自检 + if (checkInit()) + { // 已进行初始化 + ReadUUID(); + setApNet(UUID, UUID); + // 启动激活程序 + startActiveWare(); + return 0; + } + else + { // 未进行初始化 + if (GetSignID()) + { // 获取标识符成功 + setApNet(UUID, UUID); + startActiveWare(); + return 0; + } + cerr << "获取标识符失败,请重试" << endl; + return -1; + } + return 0; +} + +// 清理日志 +void clearLog() +{ + lock_guard lk(logMutex); + string logpath = "/home/orangepi/RKApp/Identification/src/nohup.out"; + size_t length = filesystem::file_size(logpath); + if (length >= MAXSIZE) + { // 清空日志 + ofstream ofs(logpath, ios::trunc); + } +} + +// 退出信号处理 +void forwardSigint(int) +{ + pid_t pgid = g_Device_pid.load(); + if (pgid > 0) + { + kill(-pgid, SIGINT); + } +} + +// 获取SIM卡号 +// 通过串口发送 AT+CCID 指令,读取并解析返回的ICCID号 +string GetSimICCID(const string &tty) +{ + int retry = 0; + while (retry < 5) + { + // 非阻塞打开,避免因 VMIN/VTIME 卡死 + int fd = open(tty.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); + if (fd < 0) + { + std::cerr << "无法打开串口: " << tty << std::endl; + return ""; + } + + // 原始模式配置 + struct termios options{}; + tcgetattr(fd, &options); + cfsetispeed(&options, B115200); + cfsetospeed(&options, B115200); + options.c_cflag |= (CLOCAL | CREAD); + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_oflag &= ~OPOST; + options.c_iflag &= ~(IXON | IXOFF | IXANY); + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = 5; // 0.5s + tcsetattr(fd, TCSANOW, &options); + + auto send_and_read = [&](const char *cmd) -> std::string + { + // 清空缓冲并发送 + tcflush(fd, TCIOFLUSH); + write(fd, cmd, strlen(cmd)); + std::string result; + char buf[256] = {0}; + // 轮询读取,累计约2秒 + for (int i = 0; i < 20; ++i) + { + int n = read(fd, buf, sizeof(buf)); + if (n > 0) + result.append(buf, n); + usleep(100000); + } + return result; + }; + + // 先试 AT+QCCID,失败再试 AT+CCID + std::string result = send_and_read("AT+QCCID\r\n"); + if (result.find("+QCCID") == std::string::npos) + { + std::string r2 = send_and_read("AT+CCID\r\n"); + if (!r2.empty()) + result += r2; + } + close(fd); + + // 打印原始回应便于调试 + std::string debug = result; + // 清理换行 + debug.erase(std::remove_if(debug.begin(), debug.end(), + [](unsigned char c) + { return c == '\r' || c == '\n'; }), + debug.end()); + // std::cout << "ICCID原始回应: " << debug << std::endl; + + // 错误重试 + if (result.find("ERROR") != std::string::npos) + { + retry++; + usleep(200000); + continue; + } + + // 解析(支持字母数字) + std::smatch m; + // +QCCID 或 +CCID 后取字母数字 + std::regex reg(R"(\+(?:Q)?CCID:\s*([0-9A-Za-z]+))"); + if (std::regex_search(debug, m, reg)) + { + std::string iccid = m[1]; + // 去掉尾部OK或非字母数字 + while (!iccid.empty() && !std::isalnum(static_cast(iccid.back()))) + iccid.pop_back(); + if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK") + iccid.erase(iccid.size() - 2); + return iccid; + } + + // 兜底:19~22位的字母数字(如尾部含 D) + std::regex reg2(R"(([0-9A-Za-z]{19,22}))"); + if (std::regex_search(debug, m, reg2)) + { + std::string iccid = m[1]; + while (!iccid.empty() && !std::isalnum(static_cast(iccid.back()))) + iccid.pop_back(); + if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK") + iccid.erase(iccid.size() - 2); + return iccid; + } + + // 进一步兜底:手工截取 +QCCID: / +CCID: 后的连续字母数字 + auto parse_after = [&](const std::string &s, const std::string &key) -> std::string + { + size_t pos = s.find(key); + if (pos == std::string::npos) + return ""; + pos += key.size(); + while (pos < s.size() && std::isspace(static_cast(s[pos]))) + pos++; + size_t start = pos; + while (pos < s.size() && std::isalnum(static_cast(s[pos]))) + pos++; + std::string iccid = (pos > start) ? s.substr(start, pos - start) : ""; + if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK") + iccid.erase(iccid.size() - 2); + return iccid; + }; + { + std::string iccid = parse_after(debug, "+QCCID:"); + if (iccid.empty()) + iccid = parse_after(debug, "+CCID:"); + if (!iccid.empty()) + return iccid; + } + + retry++; + usleep(200000); + } + return ""; +} + +// 检测是否已进行初始化 +bool checkInit() +{ + bool isInit = true; + // 上锁,防止竞争 + lock_guard lk(fileMutex); + // 读取配置文件 + ReadFile rf(filePath); + if (rf.Open() == false) + { + cerr << "文件打开失败" << endl; + isInit = false; + } + + auto lines = rf.ReadLines(); + for (auto &line : lines) + { + if (line.find("UUID:null") != string::npos) + { // 未初始化 + isInit = false; + } + } + + rf.Close(); + return isInit; +} + +// 获取标识符并保存至配置文件中 +bool GetSignID() +{ + // 获取成功状态 + bool getState = true; + bool isRunning = true; + // ICCI获取 + ICCID = GetSimICCID(); + // cout << "ICCID = " << ICCID << endl; + if (ICCID.empty()) + { + cerr << "获取ICCID失败,请检查SIM卡" << endl; + return false; + } + + int postCount = 0; + int reTryTimes = 0; + string RequestBody = format(R"({"cardNo":"{}"})", ICCID); + // cout << "RequestBody = " << RequestBody << endl; + while (isRunning) + { + // 发送请求 + // cout << "RequestBody = " << RequestBody << endl; + auto res = NetRequest::QuickPostJson(UUIDPost, RequestBody); + int code = res->status; + if (res->status == 200) + { // 请求成功 + ++postCount; + auto json = nlohmann::json::parse(res->body); + // cout << "json = " << json << endl; + if (json["code"] == 601) + { + system("clear"); + cout << "ICCID:" << ICCID << ",已发送请求次数 " << postCount << " 次" << endl; + } + else if (json["code"] == 200) + { + auto qrcode = json["data"]["qrcode"]; + UUID = qrcode; + // 保存至配置文件中 + { + lock_guard lk(fileMutex); + ReadFile rf(filePath); + if (!rf.Open()) + return false; + + auto lines = rf.ReadLines(); + for (auto &line : lines) + { + if (line.find("UUID:null") != string::npos) + { + line = format("UUID:{}", qrcode); + cout << line << endl; + } + } + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) + { + out += lines[i]; + if (i + 1 < lines.size()) + out += "\n"; + } + rf.Close(); + WriteFile wf(filePath); + wf.overwriteText(out); + isRunning = false; + } + } + else if (json["code"] == 500) + { + cout << "申请失败,请联系管理员,ICCID = " << ICCID << endl; + } + } + else + { + cerr << "请检查网络连接" << endl; + ++reTryTimes; + if (reTryTimes > 5) + { // 重试五次 + getState = false; + isRunning = false; + } + } + // 间隔两秒 + this_thread::sleep_for(2s); + } + return getState; +} + +// 设置wifi热点:名称,密码 +void setApNet(const string &name, const string &pwd) +{ + // 清空nohup.out + clearLog(); + // 确保无线接口启用 + system("sudo rfkill unblock all"); + system("sudo ip link set wlan0 up"); + // 清理占用与启用接口 + system("sudo pkill -f create_ap; sudo pkill -f dnsmasq"); + system("sudo ip link set wlan0 up"); + + // 组装SSID和密码 + std::string ssid = name.empty() ? UUID : name; + std::string pass = pwd.empty() ? UUID : pwd; + if (pass.size() < 8) + pass = "12345678"; + + // 上游接口优先默认路由 + char buf[64]{0}; + FILE *fp = popen("ip route show default | awk '{print $5}' | head -n1", "r"); + std::string upstream = "enP4p65s0"; + if (fp && fgets(buf, sizeof(buf), fp)) + { + upstream = buf; + if (!upstream.empty() && upstream.back() == '\n') + upstream.pop_back(); + } + if (fp) + pclose(fp); + string cmd = format("nohup sudo create_ap --no-virt wlan0 {} {} {} &", upstream,UUID, "12345678"); + + cout << "cmd = " << cmd << endl; + system(cmd.c_str()); +} + +// 读取配置文件,并获取标识符符 +void ReadUUID() +{ + lock_guard lk(fileMutex); + ReadFile rf(filePath); + rf.Open(); + auto lines = rf.ReadLines(); + for (auto &line : lines) + { + if (line.find("UUID:\"") != string::npos) + { + size_t start = sizeof("UUID:\"") - 1; + size_t end = line.find_last_of("\"") - start; + UUID = line.substr(start, end); + } + } + rf.Close(); +} + +// 启动设备激活程序,等待用户激活 +void startActiveWare() +{ + string cmd = "/home/orangepi/RKApp/DeviceActivate/bin/init"; + try + { + // 启动为前台子进程,父进程等待它退出 + bp::child c(cmd); + g_Device_pid = c.id(); + signal(SIGINT, forwardSigint); + c.wait(); // 等待子进程结束 + } + catch (const std::exception &e) + { + cerr << e.what() << "\n"; + } +} \ No newline at end of file diff --git a/Identification/src/makefile b/Identification/src/makefile new file mode 100644 index 0000000..3fb4525 --- /dev/null +++ b/Identification/src/makefile @@ -0,0 +1,11 @@ +all:init + +Lib=/home/orangepi/RKApp/Identification/NetraLib/src/Netra.cpp /home/orangepi/RKApp/Identification/NetraLib/src/NetRequest.cpp +Dir=-I/home/orangepi/RKApp/Identification/NetraLib/include + +init:main.cpp + g++ -g -o init main.cpp $(Lib) $(Dir) + mv init ../bin/ + +clean: + rm -rf ../bin/init \ No newline at end of file diff --git a/Identification/src/nohup.out b/Identification/src/nohup.out new file mode 100644 index 0000000..48614a0 --- /dev/null +++ b/Identification/src/nohup.out @@ -0,0 +1,443 @@ +Config dir: /tmp/create_ap.wlan0.conf.PomKvPbx +PID: 89397 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.PomKvPbx/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.dqY0BJzo +PID: 90669 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.dqY0BJzo/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: STA ee:f9:8a:d0:2d:d0 IEEE 802.11: associated +wlan0: AP-STA-CONNECTED ee:f9:8a:d0:2d:d0 +wlan0: STA ee:f9:8a:d0:2d:d0 RADIUS: starting accounting session D778B931DF91C4C1 +wlan0: STA ee:f9:8a:d0:2d:d0 WPA: pairwise key handshake completed (WPA) +wlan0: EAPOL-4WAY-HS-COMPLETED ee:f9:8a:d0:2d:d0 +wlan0: STA ee:f9:8a:d0:2d:d0 WPA: group key handshake completed (WPA) +wlan0: interface state ENABLED->DISABLED +wlan0: AP-STA-DISCONNECTED ee:f9:8a:d0:2d:d0 +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.HgAfJdWj +PID: 92972 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.HgAfJdWj/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.jCYEIC2l +PID: 94521 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.jCYEIC2l/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.O6tZHvv9 +PID: 95747 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.O6tZHvv9/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.zomkqZoi +PID: 6073 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.zomkqZoi/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.oFxCOtsK +PID: 7719 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.oFxCOtsK/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.WQ07ItUO +PID: 20344 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.WQ07ItUO/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.HjgpbEwh +PID: 21610 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.HjgpbEwh/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.OfnM3JfA +PID: 22626 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.OfnM3JfA/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.JJbBuSPG +PID: 24441 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.JJbBuSPG/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.pX7yKGQl +PID: 26376 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.pX7yKGQl/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.MOBfnqKy +PID: 5262 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.MOBfnqKy/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: STA 3e:70:82:f1:3b:46 IEEE 802.11: associated +wlan0: AP-STA-CONNECTED 3e:70:82:f1:3b:46 +wlan0: STA 3e:70:82:f1:3b:46 RADIUS: starting accounting session 6ED5FB11496C62F7 +wlan0: STA 3e:70:82:f1:3b:46 WPA: pairwise key handshake completed (WPA) +wlan0: EAPOL-4WAY-HS-COMPLETED 3e:70:82:f1:3b:46 +wlan0: STA 3e:70:82:f1:3b:46 WPA: group key handshake completed (WPA) +wlan0: interface state ENABLED->DISABLED +wlan0: AP-STA-DISCONNECTED 3e:70:82:f1:3b:46 +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.1vgFWxb7 +PID: 6614 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.1vgFWxb7/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: STA 3e:70:82:f1:3b:46 IEEE 802.11: associated +wlan0: AP-STA-CONNECTED 3e:70:82:f1:3b:46 +wlan0: STA 3e:70:82:f1:3b:46 RADIUS: starting accounting session EA15353438E0990C +wlan0: STA 3e:70:82:f1:3b:46 WPA: pairwise key handshake completed (WPA) +wlan0: EAPOL-4WAY-HS-COMPLETED 3e:70:82:f1:3b:46 +wlan0: STA 3e:70:82:f1:3b:46 WPA: group key handshake completed (WPA) +Config dir: /tmp/create_ap.wlan0.conf.KVBNKa7h +PID: 6760 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.KVBNKa7h/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: STA c6:61:e4:cc:21:b6 IEEE 802.11: associated +wlan0: AP-STA-CONNECTED c6:61:e4:cc:21:b6 +wlan0: STA c6:61:e4:cc:21:b6 RADIUS: starting accounting session EC1EC617036F6AC5 +wlan0: STA c6:61:e4:cc:21:b6 WPA: pairwise key handshake completed (WPA) +wlan0: EAPOL-4WAY-HS-COMPLETED c6:61:e4:cc:21:b6 +wlan0: STA c6:61:e4:cc:21:b6 WPA: group key handshake completed (WPA) +wlan0: interface state ENABLED->DISABLED +wlan0: AP-STA-DISCONNECTED c6:61:e4:cc:21:b6 +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.Uu1rJV2Z +PID: 9121 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.Uu1rJV2Z/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.eUBvTiPu +PID: 9947 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.eUBvTiPu/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.LUlSarOz +PID: 12353 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.LUlSarOz/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +Config dir: /tmp/create_ap.wlan0.conf.HeWk4qA5 +PID: 5723 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.HeWk4qA5/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.tn5owY5o +PID: 7312 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.tn5owY5o/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.8fdSXmBE +PID: 8097 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.8fdSXmBE/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.8q0qXfI7 +PID: 9326 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.8q0qXfI7/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.1ZLpQdtL +PID: 10483 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.1ZLpQdtL/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +Config dir: /tmp/create_ap.wlan0.conf.UBRL5Fzb +PID: 6526 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.UBRL5Fzb/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.Ul8H7ztq +PID: 7742 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.Ul8H7ztq/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.BYHPjB4H +PID: 10687 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.BYHPjB4H/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.bvmUeKpD +PID: 13214 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.bvmUeKpD/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.1vuA1WnF +PID: 14910 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.1vuA1WnF/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.9qIbqKcW +PID: 17106 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.9qIbqKcW/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.0Nc6Mgxj +PID: 18744 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.0Nc6Mgxj/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.ILCs6w3z +PID: 21113 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.ILCs6w3z/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.RSMAHJNS +PID: 22667 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.RSMAHJNS/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.5uEDtcBq +PID: 27948 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.5uEDtcBq/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done +Config dir: /tmp/create_ap.wlan0.conf.9MJc5DPx +PID: 11810 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.9MJc5DPx/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: interface state ENABLED->DISABLED + +Doing cleanup.. wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 +done +Config dir: /tmp/create_ap.wlan0.conf.vvvO6zLU +PID: 15680 +Sharing Internet using method: nat +hostapd command-line interface: hostapd_cli -p /tmp/create_ap.wlan0.conf.vvvO6zLU/hostapd_ctrl +wlan0: interface state UNINITIALIZED->ENABLED +wlan0: AP-ENABLED +wlan0: STA 8a:66:28:b0:d8:34 IEEE 802.11: associated +wlan0: AP-STA-POSSIBLE-PSK-MISMATCH 8a:66:28:b0:d8:34 +wlan0: AP-STA-POSSIBLE-PSK-MISMATCH 8a:66:28:b0:d8:34 +wlan0: AP-STA-POSSIBLE-PSK-MISMATCH 8a:66:28:b0:d8:34 +wlan0: AP-STA-POSSIBLE-PSK-MISMATCH 8a:66:28:b0:d8:34 +wlan0: STA 8a:66:28:b0:d8:34 IEEE 802.11: disassociated +wlan0: STA 8a:66:28:b0:d8:34 IEEE 802.11: disassociated +wlan0: STA 8a:66:28:b0:d8:34 IEEE 802.11: associated +wlan0: AP-STA-CONNECTED 8a:66:28:b0:d8:34 +wlan0: STA 8a:66:28:b0:d8:34 RADIUS: starting accounting session AF0169DAA09704E0 +wlan0: STA 8a:66:28:b0:d8:34 WPA: pairwise key handshake completed (WPA) +wlan0: EAPOL-4WAY-HS-COMPLETED 8a:66:28:b0:d8:34 +wlan0: STA 8a:66:28:b0:d8:34 WPA: group key handshake completed (WPA) +wlan0: interface state ENABLED->DISABLED +wlan0: AP-STA-DISCONNECTED 8a:66:28:b0:d8:34 +wlan0: AP-DISABLED +wlan0: CTRL-EVENT-TERMINATING +nl80211: deinit ifname=wlan0 disabled_11b_rates=0 + +Doing cleanup.. done diff --git a/InitAuth/conf/.env b/InitAuth/conf/.env new file mode 100644 index 0000000..952f01c --- /dev/null +++ b/InitAuth/conf/.env @@ -0,0 +1,42 @@ +#以下配置用于接收唯一标识符: +UUID:"mjb64bcdgdm7q" + +#以下配置用于获取云端发送秘钥 +ServerPwd:"1905168ed33af18c0cd09d996e82e71d" +#以下配置存储GPIO输出高低电平--状态机 +outPutMode:false + +#以下配置保存摄像头的翻转和镜像 +MEDIA_MIRROR=false +MEDIA_FLIP=false + +#以下配置存储报警距离 +NEAR_THRESHOLD=3 +MID_THRESHOLD=5 +MAX_DISTANCE=10 + +#以下配置保存方框坐标,1--左上角,2--右上角,3--右下角,4--左下角 +SAFE_1_X=0.4 +SAFE_1_Y=0.31 +SAFE_2_X=0.6 +SAFE_2_Y=0.31 +SAFE_3_X=0.9 +SAFE_3_Y=1 +SAFE_4_X=0.1 +SAFE_4_Y=1 +WARN_1_X=0.3364 +WARN_1_Y=0.5323 +WARN_2_X=0.6636 +WARN_2_Y=0.5323 +WARN_3_X=0.9 +WARN_3_Y=1 +WARN_4_X=0.1 +WARN_4_Y=1 +DANG_1_X=0.268 +DANG_1_Y=0.7709 +DANG_2_X=0.732 +DANG_2_Y=0.7709 +DANG_3_X=0.9 +DANG_3_Y=1 +DANG_4_X=0.1 +DANG_4_Y=1 \ No newline at end of file diff --git a/PyApp/Pub.py b/PyApp/Pub.py new file mode 100755 index 0000000..e96cae6 --- /dev/null +++ b/PyApp/Pub.py @@ -0,0 +1,342 @@ +import os # 文件系统操作 +import base64 # Base64编码图片 +import paho.mqtt.client as mqtt # MQTT通信 +import pymysql # 数据库操作 +import time # 时间处理 +from datetime import datetime, timedelta # 时间处理 +import serial # 串口通信 +import re # 正则匹配GPS串口数据 +import threading # 多线程上传图片 + +# ======== 配置区 ======== +IMAGE_FOLDER = "/mnt/save/warning" # 图片目录 +MQTT_BROKER = "116.147.36.110" +MQTT_PORT = 1883 +MQTT_TOPIC = "images/topic" + +DB_CONFIG = { + "host": "116.147.36.110", + "port": 13306, + "user": "root", + "password": "Wxit11335577", + "database": "mqtt" +} + +SERIAL_PORT = "/dev/ttyUSB3" +SERIAL_BAUDRATE = 115200 + +ICCID_SERIAL_PORT = "/dev/ttyUSB2" +ICCID_BAUDRATE = 115200 +ICCID_READ_INTERVAL_SEC = 30 # 周期性刷新 ICCID(服务频繁读取) + + +MAX_UPLOADED_SIZE = 100000 # 最大上传记录数 +NOW_SIZE = 0 # 当前记录数 + +LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "upload.log") + +# ======== 全局变量与线程锁 ======== +last_lon, last_lat = None, None # 上次读取到的GPS +ser = None # GPS串口对象 +iccid_ser = None # ICCID串口对象 +serial_lock = threading.Lock() +iccid_lock = threading.Lock() +uploaded_lock = threading.Lock() +uploaded = {} # 保存已上传记录的文件及时间 +current_iccid = None # 当前读取到的 SIM ICCID + +# ======== 工具函数 ======== +def write_log(message): + """写入日志文件""" + with open(LOG_FILE, "a") as logf: + logf.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}\n") + +# def extract_gps_data(serial_line): +# """ +# 从串口数据中提取经纬度(只处理$GNGLL格式) +# 示例数据: $GNGLL,3114.72543,N,12130.62735,E,... +# 只做方向转换,不转换度分格式 +# 返回 (lon, lat),都为float,带正负号 +# """ +# match = re.search(r'\$GNGLL,(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line) +# if match: +# lat = float(match.group(1)) +# lat_dir = match.group(2) +# lon = float(match.group(3)) +# lon_dir = match.group(4) + +# if lat_dir == 'S': +# lat = -lat +# if lon_dir == 'W': +# lon = -lon +# return lon, lat +# return None, None + + +def read_iccid(): + """ + 从 /dev/ttyUSB2 读取 SIM 卡 ICCID,发送 AT+QCCID,带重试、短超时。 + 返回字符串 ICCID 或 None。 + """ + global iccid_ser, current_iccid + with iccid_lock: + try: + if iccid_ser is None or not iccid_ser.is_open: + iccid_ser = serial.Serial(ICCID_SERIAL_PORT, ICCID_BAUDRATE, timeout=1) + # 清空缓冲 + iccid_ser.reset_input_buffer() + iccid_ser.reset_output_buffer() + # 发送指令 + iccid_ser.write(b"AT+QCCID\r\n") + iccid_ser.flush() + + # 读取多次,拼接回应 + resp = "" + for _ in range(10): + chunk = iccid_ser.readline().decode(errors="ignore") + if chunk: + resp += chunk + time.sleep(0.1) + + # 解析 ICCID + # 常见返回:\r\n+QCCID: 8986....\r\nOK\r\n 或 +QCCID:xxxxxxxx + m = re.search(r"\+QCCID:\s*([0-9A-Za-z]+)", resp) + if m: + current_iccid = m.group(1).strip() + write_log(f"读取ICCID成功: {current_iccid}") + return current_iccid + + # 兜底:匹配19~22位数字 + m2 = re.search(r"(\d{19,22})", resp) + if m2: + current_iccid = m2.group(1) + write_log(f"读取ICCID(兜底)成功: {current_iccid}") + return current_iccid + + write_log(f"读取ICCID失败: 无有效回应 -> {resp.strip()}") + return None + except Exception as e: + write_log(f"读取ICCID异常: {e}") + return None + +def iccid_refresh_worker(): + """ + 后台线程:周期刷新 ICCID,确保服务频繁读取。 + """ + write_log("ICCID刷新线程启动") + while True: + try: + read_iccid() + except Exception as e: + write_log(f"ICCID刷新异常: {e}") + time.sleep(ICCID_READ_INTERVAL_SEC) + +def extract_gps_data(serial_line): + """ + 从串口数据中提取经纬度,支持$GNGLL、$GNRMC、$GNGGA格式 + 只做方向正负号转换,不转换度分格式 + 返回 (lon, lat),float,带正负号 + """ + # 先匹配$GNGLL(你之前的) + match = re.search(r'\$GNGLL,(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line) + if match: + lat = float(match.group(1)) + lat_dir = match.group(2) + lon = float(match.group(3)) + lon_dir = match.group(4) + + if lat_dir == 'S': + lat = -lat + if lon_dir == 'W': + lon = -lon + + return lon, lat + + # 尝试匹配$GNRMC + match = re.search(r'\$GNRMC,[^,]*,[AV],(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line) + if match: + lat = float(match.group(1)) + lat_dir = match.group(2) + lon = float(match.group(3)) + lon_dir = match.group(4) + + if lat_dir == 'S': + lat = -lat + if lon_dir == 'W': + lon = -lon + + return lon, lat + + # 尝试匹配$GNGGA + match = re.search(r'\$GNGGA,[^,]*,(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line) + if match: + lat = float(match.group(1)) + lat_dir = match.group(2) + lon = float(match.group(3)) + lon_dir = match.group(4) + + if lat_dir == 'S': + lat = -lat + if lon_dir == 'W': + lon = -lon + + return lon, lat + + return None, None + +def read_gps_data(): + global last_lon, last_lat, ser + with serial_lock: + try: + if ser is None or not ser.is_open: + ser = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE, timeout=1) + + line = ser.readline().decode('utf-8').strip() + if line: + # 只对特定句子类型尝试解析并写日志 + if line.startswith(('$GNGLL', '$GNRMC', '$GNGGA')): + lon, lat = extract_gps_data(line) + if lon is not None and lat is not None: + last_lon, last_lat = lon, lat + return lon, lat + else: + write_log(f"无效的GPS数据: {line}") + else: + # 忽略其他NMEA语句 + pass + except Exception as e: + write_log(f"串口读取失败: {e}") + return 1201, 3129 + + +# def read_gps_data(): +# """ +# 从串口读取一行数据并尝试解析GPS经纬度。 +# 不成功时返回 (None, None) +# """ +# global last_lon, last_lat, ser +# with serial_lock: +# try: +# if ser is None or not ser.is_open: +# ser = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE, timeout=1) + +# line = ser.readline().decode('utf-8').strip() +# if line: +# lon, lat = extract_gps_data(line) +# if lon is not None and lat is not None: +# last_lon, last_lat = lon, lat # 更新最新值 +# return lon, lat +# else: +# write_log(f"无效的GPS数据: {line}") +# except Exception as e: +# write_log(f"串口读取失败: {e}") +# return 1201, 3129 + +def save_to_db(topic, payload, lon, lat, card_no, create_time): + """ + 将图片、经纬度、ICCID和创建时间写入数据库(card_no, create_time) + """ + try: + db = pymysql.connect(**DB_CONFIG) + cursor = db.cursor() + sql = "INSERT INTO mqtt_messages (topic, payload, lon, lat, card_no, create_time) VALUES (%s, %s, %s, %s, %s, %s)" + cursor.execute(sql, (topic, payload, lon, lat, card_no, create_time)) + db.commit() + write_log("数据库插入成功") + except Exception as e: + write_log(f"数据库插入失败: {e}") + finally: + try: + db.close() + except: + pass + +def publish_image(image_path): + """ + 发布图片到MQTT、写入数据库,并附带经纬度、SIM ICCID、创建时间 + """ + with open(image_path, "rb") as f: + img_bytes = f.read() + img_b64 = base64.b64encode(img_bytes).decode("utf-8") + + # MQTT发布 + client = mqtt.Client() + client.connect(MQTT_BROKER, MQTT_PORT) + client.publish(MQTT_TOPIC, img_b64) + client.disconnect() + + # GPS + lon, lat = read_gps_data() + if lon is None or lat is None: + write_log("GPS读取失败,尝试使用上一次位置") + lon, lat = last_lon, last_lat + + # ICCID(若当前为空则尝试即时读取一次) + card_no = current_iccid or read_iccid() + + # 创建时间(ISO格式或数据库期望格式) + create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + write_log(f"准备入库: lon={lon}, lat={lat}, card_no={card_no}, create_time={create_time}") + save_to_db(MQTT_TOPIC, img_b64, lon, lat, card_no, create_time) + + +def upload_worker(img_path): + """上传线程任务,处理一张图片上传""" + try: + publish_image(img_path) + write_log("上传线程结束: " + img_path + ":\t成功!") + except Exception as e: + write_log(f"上传图片 {img_path} 时出错: {e}") + +def clean_uploaded(): + """ + 清理7天前的已上传图片记录,避免内存泄漏 + """ + with uploaded_lock: + expiration_time = datetime.now() - timedelta(days=7) + keys_to_remove = [key for key, value in uploaded.items() if value < expiration_time] + for key in keys_to_remove: + del uploaded[key] + if keys_to_remove: + write_log(f"清理已上传图片记录,移除 {len(keys_to_remove)} 条记录") + +# ======== 主循环程序入口 ======== +if __name__ == "__main__": + # 启动 ICCID 周期刷新线程 + threading.Thread(target=iccid_refresh_worker, daemon=True).start() + + last_clean_time = time.time() + try: + while True: + now = time.time() + for filename in os.listdir(IMAGE_FOLDER): + if filename.lower().endswith((".jpg", ".jpeg", ".png", ".bmp", ".gif")): + img_path = os.path.join(IMAGE_FOLDER, filename) + mtime = os.path.getmtime(img_path) + # 最近10秒内新创建,且未上传 + if now - mtime <= 10: + with uploaded_lock: + if img_path in uploaded: + continue + write_log("开始上传图片: " + img_path) + t = threading.Thread(target=upload_worker, args=(img_path,)) + t.start() + with uploaded_lock: + uploaded[img_path] = datetime.now() + # 每小时清理一次上传记录 + if time.time() - last_clean_time > 3600: + clean_uploaded() + last_clean_time = time.time() + + time.sleep(2) + except Exception as e: + write_log(f"主程序异常: {e}") + finally: + try: + if ser is not None and ser.is_open: + ser.close() + if iccid_ser is not None and iccid_ser.is_open: + iccid_ser.close() + except: + pass \ No newline at end of file diff --git a/PyApp/upload.log b/PyApp/upload.log new file mode 100644 index 0000000..c195758 --- /dev/null +++ b/PyApp/upload.log @@ -0,0 +1,7832 @@ +2025-12-06 11:20:09 - ICCID刷新线程启动 +2025-12-06 11:20:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:20:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:21:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:22:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:22:37 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:23:07 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:23:37 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:24:07 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:24:37 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:25:07 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:25:37 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:26:07 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:26:37 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:27:07 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:27:37 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:28:07 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:28:38 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:29:08 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:29:38 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:30:08 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:30:38 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:31:08 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:31:38 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:32:08 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:32:38 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:33:08 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:33:38 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:34:08 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:34:38 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:35:08 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:35:38 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:36:08 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 11:37:32 - ICCID刷新线程启动 +2025-12-06 11:37:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:38:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:38:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:39:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:40:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:40:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:41:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:41:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:42:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:43:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:43:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:44:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:45:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:45:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:46:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:46:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:47:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:48:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:48:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:49:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:50:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:50:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:51:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:51:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:52:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:53:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:53:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:54:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:54:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:55:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:56:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:56:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:57:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:58:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:58:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:59:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 11:59:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:00:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:01:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:01:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:02:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:02:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:03:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:04:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:04:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:05:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:06:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:06:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:07:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:07:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:08:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:09:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:09:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:10:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:10:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:11:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:12:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:12:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:13:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:14:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:14:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:15:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:15:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:16:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:17:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:17:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:18:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:19:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:19:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:20:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:20:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:21:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:22:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:22:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:23:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:23:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:24:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:25:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:25:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:26:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:27:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:27:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:28:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:28:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:29:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:30:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:30:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:31:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:31:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:32:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:33:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:33:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:34:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:35:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:35:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:36:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:36:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:37:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:38:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:38:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:39:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:40:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:40:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:41:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:41:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:42:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:43:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:43:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:44:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:44:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:45:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:46:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:46:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:47:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:48:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:48:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:49:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:49:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:50:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:51:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:51:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:52:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:52:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:53:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:54:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:54:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:55:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:56:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:56:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:57:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:57:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:58:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:59:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 12:59:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 13:00:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 13:00:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 13:01:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 13:02:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-06 13:02:44 - 读取ICCID异常: write failed: [Errno 19] No such device +2025-12-06 13:03:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:03:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:04:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:04:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:05:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:05:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:06:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:06:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:07:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:07:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:08:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:08:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:09:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:09:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:10:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:10:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:11:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:11:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:12:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:12:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:13:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:13:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:14:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:14:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:15:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:15:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:16:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:16:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:17:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:17:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:18:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:18:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:19:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:19:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:20:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:20:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:21:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:21:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:22:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:22:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:23:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:23:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:24:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:24:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:25:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:25:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:26:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:26:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:27:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:27:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:28:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:28:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:29:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:29:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:30:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:30:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:31:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:31:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:32:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:32:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:33:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:33:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:34:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:34:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:35:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:35:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:36:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:36:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:37:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:37:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:38:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:38:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:39:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:39:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:40:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:40:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:41:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:41:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:42:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:42:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:43:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:43:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:44:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:44:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:45:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:45:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:46:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:46:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:47:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:47:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:48:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:48:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:49:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:49:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:50:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:50:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:51:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:51:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:52:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:52:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:53:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:53:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:54:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:54:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:55:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:55:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:56:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:56:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:57:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:57:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:58:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:58:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:59:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 13:59:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:00:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:00:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:01:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:01:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:02:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:02:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:03:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:03:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:04:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:04:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:05:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:05:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:06:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:06:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:07:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:07:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:08:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:08:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:09:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:09:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:10:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:10:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:11:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:11:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:12:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:12:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:13:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:13:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:14:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:14:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:15:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:15:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:16:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:16:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:17:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:17:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:18:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:18:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:19:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:19:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:20:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:20:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:21:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:21:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:22:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:22:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:23:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:23:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:24:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:24:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:25:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:25:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:26:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:26:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:27:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:27:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:28:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:28:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:29:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:29:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:30:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:30:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:31:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:31:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:32:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:32:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:33:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:33:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:34:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:34:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:35:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:35:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:36:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:36:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:37:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:37:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:38:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:38:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:39:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:39:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:40:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:40:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:41:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:41:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:42:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:42:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:43:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:43:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:44:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:44:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:45:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:45:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:46:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:46:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:47:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:47:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:48:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:48:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:49:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:49:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:50:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:50:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:51:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:51:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:52:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:52:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:53:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:53:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:54:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:54:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:55:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:55:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:56:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:56:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:57:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:57:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:58:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:58:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:59:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 14:59:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:00:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:00:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:01:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:01:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:02:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:02:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:03:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:03:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:04:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:04:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:05:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:05:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:06:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:06:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:07:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:07:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:08:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:08:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:09:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:09:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:10:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:10:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:11:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:11:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:12:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:12:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:13:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:13:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:14:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:14:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:15:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:15:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:16:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:16:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:17:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:17:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:18:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:18:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:19:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:19:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:20:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:20:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:21:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:21:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:22:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:22:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:23:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:23:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:24:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:24:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:25:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:25:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:26:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:26:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:27:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:27:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:28:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:28:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:29:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:29:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:30:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:30:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:31:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:31:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:32:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:32:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:33:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:33:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:34:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:34:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:35:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:35:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:36:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:36:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:37:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:37:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:38:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:38:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:39:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:39:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:40:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:40:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:41:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:41:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:42:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:42:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:43:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:43:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:44:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:44:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:45:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:45:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:46:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:46:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:47:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:47:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:48:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:48:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:49:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:49:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:50:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:50:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:51:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:51:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:52:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:52:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:53:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:53:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:54:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:54:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:55:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:55:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:56:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:56:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:57:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:57:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:58:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:58:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:59:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 15:59:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:00:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:00:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:01:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:01:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:02:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:02:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:03:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:03:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:04:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:04:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:05:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:05:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:06:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:06:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:07:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:07:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:08:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:08:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:09:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:09:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:10:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:10:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:11:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:11:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:12:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:12:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:13:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:13:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:14:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:14:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:15:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:15:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:16:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:16:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:17:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:17:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:18:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:18:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:19:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:19:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:20:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:20:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:21:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:21:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:22:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:22:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:23:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:23:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:24:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:24:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:25:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:25:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:26:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:26:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:27:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:27:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:28:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:28:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:29:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:29:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:30:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:30:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:31:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:31:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:32:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:32:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:33:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:33:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:34:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:34:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:35:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:35:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:36:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:36:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:37:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:37:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:38:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:38:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:39:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:39:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:40:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:40:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:41:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:41:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:42:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:42:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:43:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:43:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:44:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:44:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:45:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:45:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:46:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:46:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:47:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:47:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:48:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:48:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:49:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:49:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:50:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:50:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:51:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:51:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:52:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:52:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:53:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:53:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:54:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:54:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:55:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:55:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:56:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:56:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:57:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:57:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:58:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:58:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:59:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 16:59:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:00:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:00:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:01:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:01:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:02:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:02:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:03:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:03:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:04:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:04:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:05:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:05:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:06:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:06:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-06 17:07:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-07 15:44:00 - ICCID刷新线程启动 +2025-12-07 15:44:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:44:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:45:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:45:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:46:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:47:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:47:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:48:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:49:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:49:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:50:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:50:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:51:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:52:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:52:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:53:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:54:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:54:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:55:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:55:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:56:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:57:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:57:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:58:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:58:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 15:59:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:00:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:00:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:01:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:02:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:02:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:03:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:03:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:04:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:05:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:05:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:06:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:06:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:07:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:08:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:08:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:09:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:10:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:10:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:11:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:11:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:12:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:13:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:13:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:14:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:14:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:15:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:16:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:16:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:17:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:18:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:18:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:19:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:19:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:20:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:21:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:21:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:22:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:23:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:23:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:24:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:24:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:25:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:26:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:26:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:27:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:27:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:28:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:29:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:29:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:30:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:31:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:31:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:32:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:32:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:33:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:34:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:34:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:35:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:35:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:36:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:37:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:37:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:38:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:39:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:39:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:40:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:40:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:41:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:42:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:42:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:43:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:44:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:44:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:45:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:45:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:46:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:47:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:47:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:48:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:48:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:49:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:50:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:50:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:51:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:52:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:52:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:53:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:53:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:54:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:55:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:55:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:56:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:56:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:57:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:58:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:58:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 16:59:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:00:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:00:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:01:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:01:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:02:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:03:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:03:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:04:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:04:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:05:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:06:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:06:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:07:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:08:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:08:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:09:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:09:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:10:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:11:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:11:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:12:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:13:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:13:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:14:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:14:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:15:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:16:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:16:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:17:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:17:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:18:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:19:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:19:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:20:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:21:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:21:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:22:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:22:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:23:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:24:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:24:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:25:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:25:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:26:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:27:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:27:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:28:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:29:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:29:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:30:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:30:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-07 17:31:38 - ICCID刷新线程启动 +2025-12-08 10:07:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:08:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:09:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:09:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:10:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:10:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-08 10:11:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-08 10:11:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-08 10:12:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-08 10:12:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-08 10:13:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-08 10:15:39 - ICCID刷新线程启动 +2025-12-08 10:15:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:16:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:17:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:17:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:18:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:18:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:19:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:20:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:20:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:21:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:21:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:22:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:23:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:23:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:24:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:25:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:25:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:26:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:26:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:27:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:28:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:28:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:29:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:29:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:30:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:31:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:31:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:32:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:33:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:33:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:34:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:34:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:35:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:36:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:36:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:37:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:37:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:38:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:39:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:39:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:40:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:41:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:41:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:42:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:42:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:43:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:44:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:44:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:45:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:46:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:46:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:47:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:47:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:48:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:49:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:49:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:50:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:50:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:51:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:52:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:52:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:53:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:54:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:54:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:55:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:55:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:56:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:57:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:57:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:58:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:58:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 10:59:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:00:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:00:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:01:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:02:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:02:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:03:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:03:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:04:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:05:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:05:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:06:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:07:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:07:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:08:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:08:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:09:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:10:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:10:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:11:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:11:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:12:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:13:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:13:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:14:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:15:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:15:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:16:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:16:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:17:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:18:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:18:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:19:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:19:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:20:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:21:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:21:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:22:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:23:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:23:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:24:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:24:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:25:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:26:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:26:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:27:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:28:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:28:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:29:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:29:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:30:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:31:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:31:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:32:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:32:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:33:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:34:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:34:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:35:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:36:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:36:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:37:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:37:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:38:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:39:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:39:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:40:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:40:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:41:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:42:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:42:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:43:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:44:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:44:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:45:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:45:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:46:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:47:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:47:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:48:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:48:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:49:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:50:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:50:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:51:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:52:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:52:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:53:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:53:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:54:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:55:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:55:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:56:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:57:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:57:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:58:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:58:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 11:59:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:00:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:00:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:01:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:01:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:02:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:03:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:03:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:04:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:05:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:05:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:06:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:06:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:07:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:08:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:08:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:09:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:09:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:10:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:11:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:11:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:12:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:13:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:13:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:14:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:14:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:15:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:16:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:16:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:17:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:18:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:18:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:19:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:19:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:20:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:21:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:21:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:22:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:22:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:23:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:24:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:24:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:25:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:26:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:26:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:27:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:27:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:28:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:29:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:29:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:30:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:30:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:31:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:32:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:32:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:33:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:34:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:34:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:35:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:35:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:36:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:37:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:37:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:38:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:39:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:39:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:40:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:40:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:41:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:42:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:42:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-08 12:43:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-08 12:43:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-08 12:44:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-08 12:44:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 12:25:43 - ICCID刷新线程启动 +2025-12-12 12:25:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 12:26:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:32:23 - ICCID刷新线程启动 +2025-12-12 15:32:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:33:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:33:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:34:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:34:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:35:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:36:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:36:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:37:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:38:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:38:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:39:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:39:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:40:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:41:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:41:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:42:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:42:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:43:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:44:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:44:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:45:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:46:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:46:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:47:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:47:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:48:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:49:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:49:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:50:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:50:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 15:51:29 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:51:59 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:52:29 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:52:59 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:53:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:54:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:54:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:55:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:55:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:56:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:56:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:57:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:57:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:58:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:58:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:59:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 15:59:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:00:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:00:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:01:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:01:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:02:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:02:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:03:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:03:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:04:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:04:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:05:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:05:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:06:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:06:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:07:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:07:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:08:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:08:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:09:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:09:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:10:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:10:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:11:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:11:30 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:12:00 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:12:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:13:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:13:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:14:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:14:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:15:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:15:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:16:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:16:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:17:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:17:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:18:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:18:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:19:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:19:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:20:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:20:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:21:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:21:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:22:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:22:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:23:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:23:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:24:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:24:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:25:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:25:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:26:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:26:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:27:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:27:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:28:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:28:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:29:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:29:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:30:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:30:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:31:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:31:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:32:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:32:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:33:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:33:31 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:34:01 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:34:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:35:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:35:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:36:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:36:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:37:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:37:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:38:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:38:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:39:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:39:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:40:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:40:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:41:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:41:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:42:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:42:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:43:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:43:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:44:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:44:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:45:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:45:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:46:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:46:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:47:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:47:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:48:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:48:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:49:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:49:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:50:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:50:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:51:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:51:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:52:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:52:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:53:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:53:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:54:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:54:32 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:55:02 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:55:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:56:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:56:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:57:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:57:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:58:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:58:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:59:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 16:59:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:00:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:00:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:01:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:01:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:02:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:02:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:03:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:03:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:04:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:04:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:05:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:05:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:06:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:06:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:07:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:07:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:08:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:08:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:09:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:09:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:10:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:10:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:11:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:11:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:12:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:12:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:13:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:13:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:14:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:14:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:15:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:15:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:16:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:16:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:17:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:17:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:18:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:53:22 - ICCID刷新线程启动 +2025-12-12 17:53:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 17:54:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 17:54:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 17:55:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 17:55:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 17:56:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 17:57:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-12 17:57:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:58:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-12 17:58:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 10:13:03 - ICCID刷新线程启动 +2025-12-15 10:13:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 09:17:12 - ICCID刷新线程启动 +2025-12-15 09:17:12 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-15 12:17:09 - ICCID刷新线程启动 +2025-12-15 12:17:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 12:31:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:32:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:32:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:33:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:33:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:34:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:34:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:35:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:35:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:36:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:36:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:37:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:37:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:38:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:38:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:39:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:39:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:40:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:40:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:41:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:41:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:42:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:42:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:43:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:43:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:44:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:44:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:45:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:45:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:46:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:46:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:47:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:47:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:48:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:48:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:49:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:49:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:50:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:50:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:51:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:51:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:52:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:52:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:53:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:53:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:54:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:54:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:55:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:55:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:56:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:56:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:57:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:57:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:58:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:58:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:59:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 12:59:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:00:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:00:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:01:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:01:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:02:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:02:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:03:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:03:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:04:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:04:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:05:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:05:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:06:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:06:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:07:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:07:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:08:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:08:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:10:12 - ICCID刷新线程启动 +2025-12-15 13:10:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:10:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:11:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:12:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:12:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:13:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:13:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:14:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:15:19 - ICCID刷新线程启动 +2025-12-15 13:15:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:16:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:16:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:17:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:17:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:18:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:19:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:19:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:20:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:20:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:21:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:22:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:22:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:23:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:24:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:24:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:25:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:25:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:26:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:36:37 - ICCID刷新线程启动 +2025-12-15 13:36:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:37:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:37:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:38:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:39:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:39:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:40:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:40:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:41:25 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:41:55 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:42:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:42:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:43:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:43:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:44:26 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:44:56 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 13:17:09 - ICCID刷新线程启动 +2025-12-15 13:17:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:51:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:52:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:52:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:53:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:54:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:54:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:55:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:56:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:56:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:57:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:57:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:58:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:59:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 13:59:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 14:00:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 14:01:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 14:01:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 14:02:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 14:02:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:03:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:03:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:04:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:04:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:05:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:05:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:06:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:06:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:07:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:07:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:08:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:08:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:09:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:09:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:10:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:10:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:11:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:11:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:12:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:12:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:13:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:13:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:14:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:14:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:15:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:15:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:16:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:16:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:17:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:17:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:18:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:18:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:19:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:19:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:20:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:20:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:21:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:21:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:22:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:22:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:23:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:23:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:24:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:24:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:25:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:25:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:26:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:26:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:27:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:27:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:28:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:28:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:29:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:29:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:30:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:30:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:31:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:31:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:32:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:32:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:33:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:33:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:34:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:34:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:35:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:35:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:36:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:36:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:37:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:37:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:38:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:38:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:39:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:39:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:40:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:40:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:41:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:41:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:42:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:42:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:43:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:43:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:44:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:44:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:45:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:45:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:46:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:46:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:47:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:47:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:48:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:48:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:49:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:49:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:50:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:50:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:51:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:51:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:52:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:52:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:53:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:53:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:54:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:54:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:55:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:55:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:56:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:56:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:57:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:57:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:58:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:58:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:59:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 14:59:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:00:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:00:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:01:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:01:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:02:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:02:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:03:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:03:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:04:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:04:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:05:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:05:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:06:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:06:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:07:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:07:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:08:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:08:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:09:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:09:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:10:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:10:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:11:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:11:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:12:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:12:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:13:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:13:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:14:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:14:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:15:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:15:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:16:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:16:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:17:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:17:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:18:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:18:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:19:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:19:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:20:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:20:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:21:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:21:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:22:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:22:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:23:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:23:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:24:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:24:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:25:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:25:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:26:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:26:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:27:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:27:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:28:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:28:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:29:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:29:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:30:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:30:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:31:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:31:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:32:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:32:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:33:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:33:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:34:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:34:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:35:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:35:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:36:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:36:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:37:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:37:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:38:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:38:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:39:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:39:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:40:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:40:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:41:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:41:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:42:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:42:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:43:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:43:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:44:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:44:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:45:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:45:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:46:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:46:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:47:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:47:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:48:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:48:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:49:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:49:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:50:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:50:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:51:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:51:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:52:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:52:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:53:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:53:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:54:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:54:49 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:55:19 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:55:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:56:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:56:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:57:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:57:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:58:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:58:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:59:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 15:59:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:00:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:00:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:01:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:01:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:02:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:02:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:03:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:03:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:04:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:04:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:05:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:05:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:06:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:06:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:07:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:07:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:08:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:08:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:09:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:09:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:10:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:10:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:11:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:11:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:12:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:12:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:13:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:13:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:14:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:14:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:15:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:15:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:16:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:16:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:17:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:17:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:18:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:18:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:19:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:19:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:20:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:20:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:21:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:21:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:22:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:22:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:23:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:23:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:24:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:24:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:25:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:25:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:26:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:26:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:27:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:27:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:28:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:28:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:29:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:29:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:30:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:30:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:31:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:31:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:32:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:32:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:33:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:33:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:34:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:34:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:35:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:35:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:36:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:36:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:37:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:37:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:38:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:38:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:39:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:39:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:40:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:40:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:41:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:41:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:42:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:42:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:43:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:43:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:44:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:44:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:45:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:45:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:46:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:46:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:47:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:47:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:48:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:48:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:49:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:49:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:50:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:50:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:51:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:51:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:52:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:52:52 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:53:22 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:53:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:54:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:54:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:55:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:55:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:56:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:56:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:57:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:57:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:58:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:58:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:59:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 16:59:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:00:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:00:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:01:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:01:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:02:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:02:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:03:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:03:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:04:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:04:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:05:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:05:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:06:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:06:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:07:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:07:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:08:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:08:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:09:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:09:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:10:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:10:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:11:23 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:11:53 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:12:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:12:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:13:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:13:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:14:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:14:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:15:24 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:15:54 - 读取ICCID异常: (5, 'Input/output error') +2025-12-15 17:20:11 - ICCID刷新线程启动 +2025-12-15 17:20:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 17:20:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 17:21:25 - 读取ICCID异常: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-15 17:23:04 - ICCID刷新线程启动 +2025-12-15 17:23:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-15 17:23:47 - 读取ICCID异常: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-16 11:06:14 - ICCID刷新线程启动 +2025-12-16 11:06:14 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:11:11 - ICCID刷新线程启动 +2025-12-18 12:11:11 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:12:54 - ICCID刷新线程启动 +2025-12-18 12:12:54 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:17:25 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:17:25 - ICCID刷新线程启动 +2025-12-18 12:19:56 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:19:56 - ICCID刷新线程启动 +2025-12-18 12:27:02 - ICCID刷新线程启动 +2025-12-18 12:27:02 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:27:49 - ICCID刷新线程启动 +2025-12-18 12:27:49 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:58:29 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:58:48 - ICCID刷新线程启动 +2025-12-18 12:58:48 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:59:55 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:59:55 - ICCID刷新线程启动 +2025-12-18 13:00:57 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 13:03:11 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 13:03:11 - ICCID刷新线程启动 +2025-12-18 13:05:39 - ICCID刷新线程启动 +2025-12-18 13:05:39 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning' +2025-12-18 12:17:11 - ICCID刷新线程启动 +2025-12-18 12:17:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:08:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:09:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:09:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:10:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:11:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:11:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:12:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:12:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:13:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:14:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:14:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:15:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:16:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:16:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:17:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:17:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:18:27 - 读取ICCID异常: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-18 13:18:57 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 13:19:27 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 13:19:57 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 13:20:27 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 13:20:57 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 13:21:27 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 13:21:57 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 13:25:44 - ICCID刷新线程启动 +2025-12-18 13:25:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:31:12 - ICCID刷新线程启动 +2025-12-18 13:31:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:31:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:32:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:33:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:33:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:34:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:35:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:35:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:36:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:36:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:37:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:38:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:41:05 - ICCID刷新线程启动 +2025-12-18 13:41:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:41:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:42:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:43:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:43:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:44:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:44:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:45:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:46:20 - ICCID刷新线程启动 +2025-12-18 13:46:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:47:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:47:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:49:33 - ICCID刷新线程启动 +2025-12-18 13:49:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:50:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:50:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:51:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:52:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:52:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:53:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:53:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:54:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:55:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:55:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:56:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:57:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:57:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:58:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:58:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 13:59:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:00:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:00:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:01:22 - 开始上传图片: /mnt/save/warning/alarm_20251218_140122.jpg +2025-12-18 14:01:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:01:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 14:01:23 +2025-12-18 14:01:24 - 数据库插入成功 +2025-12-18 14:01:24 - 上传线程结束: /mnt/save/warning/alarm_20251218_140122.jpg: 成功! +2025-12-18 14:02:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:02:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:03:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:03:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:04:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:05:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:05:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:06:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:06:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:07:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:08:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:08:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:09:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:10:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:10:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:11:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:11:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:12:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:13:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:13:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:14:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:14:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:15:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:16:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:16:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:17:20 - 开始上传图片: /mnt/save/warning/alarm_20251218_141719.jpg +2025-12-18 14:17:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 14:17:21 +2025-12-18 14:17:22 - 数据库插入成功 +2025-12-18 14:17:22 - 上传线程结束: /mnt/save/warning/alarm_20251218_141719.jpg: 成功! +2025-12-18 14:17:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:18:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:18:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:19:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:19:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:20:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:21:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:21:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:21:56 - 开始上传图片: /mnt/save/warning/alarm_20251218_142155.jpg +2025-12-18 14:21:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 14:21:58 +2025-12-18 14:21:58 - 数据库插入成功 +2025-12-18 14:21:58 - 上传线程结束: /mnt/save/warning/alarm_20251218_142155.jpg: 成功! +2025-12-18 14:22:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:22:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:23:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:24:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:24:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:25:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:26:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:26:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:27:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:27:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:28:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:29:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:29:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:30:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:31:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:31:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:32:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:32:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:32:58 - 开始上传图片: /mnt/save/warning/alarm_20251218_143256.jpg +2025-12-18 14:32:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 14:32:59 +2025-12-18 14:33:00 - 数据库插入成功 +2025-12-18 14:33:00 - 上传线程结束: /mnt/save/warning/alarm_20251218_143256.jpg: 成功! +2025-12-18 14:33:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:34:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:34:12 - 开始上传图片: /mnt/save/warning/alarm_20251218_143411.jpg +2025-12-18 14:34:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 14:34:13 +2025-12-18 14:34:14 - 数据库插入成功 +2025-12-18 14:34:14 - 上传线程结束: /mnt/save/warning/alarm_20251218_143411.jpg: 成功! +2025-12-18 14:34:26 - 开始上传图片: /mnt/save/warning/alarm_20251218_143426.jpg +2025-12-18 14:34:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 14:34:27 +2025-12-18 14:34:28 - 数据库插入成功 +2025-12-18 14:34:28 - 上传线程结束: /mnt/save/warning/alarm_20251218_143426.jpg: 成功! +2025-12-18 14:34:32 - 开始上传图片: /mnt/save/warning/alarm_20251218_143430.jpg +2025-12-18 14:34:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 14:34:33 +2025-12-18 14:34:34 - 数据库插入成功 +2025-12-18 14:34:34 - 上传线程结束: /mnt/save/warning/alarm_20251218_143430.jpg: 成功! +2025-12-18 14:34:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:35:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:35:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:36:28 - 开始上传图片: /mnt/save/warning/alarm_20251218_143626.jpg +2025-12-18 14:36:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 14:36:29 +2025-12-18 14:36:30 - 数据库插入成功 +2025-12-18 14:36:30 - 上传线程结束: /mnt/save/warning/alarm_20251218_143626.jpg: 成功! +2025-12-18 14:36:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:37:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:37:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:38:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 14:39:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 16:18:50 - ICCID刷新线程启动 +2025-12-18 16:18:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 16:19:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 16:20:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-18 16:20:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:21:10 - 开始上传图片: /mnt/save/warning/alarm_20251218_162109.jpg +2025-12-18 16:21:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:21:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:21:11 +2025-12-18 16:21:11 - 数据库插入成功 +2025-12-18 16:21:11 - 上传线程结束: /mnt/save/warning/alarm_20251218_162109.jpg: 成功! +2025-12-18 16:21:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:21:44 - 开始上传图片: /mnt/save/warning/alarm_20251218_162144.jpg +2025-12-18 16:21:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:21:45 +2025-12-18 16:21:45 - 数据库插入成功 +2025-12-18 16:21:45 - 上传线程结束: /mnt/save/warning/alarm_20251218_162144.jpg: 成功! +2025-12-18 16:22:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:22:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:23:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:23:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:24:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:24:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:25:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:25:28 - 开始上传图片: /mnt/save/warning/alarm_20251218_162527.jpg +2025-12-18 16:25:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:25:29 +2025-12-18 16:25:30 - 数据库插入成功 +2025-12-18 16:25:30 - 上传线程结束: /mnt/save/warning/alarm_20251218_162527.jpg: 成功! +2025-12-18 16:25:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:25:44 - 开始上传图片: /mnt/save/warning/alarm_20251218_162544.jpg +2025-12-18 16:25:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:25:45 +2025-12-18 16:25:46 - 数据库插入成功 +2025-12-18 16:25:46 - 上传线程结束: /mnt/save/warning/alarm_20251218_162544.jpg: 成功! +2025-12-18 16:26:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:26:34 - 开始上传图片: /mnt/save/warning/alarm_20251218_162634.jpg +2025-12-18 16:26:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:26:35 +2025-12-18 16:26:36 - 数据库插入成功 +2025-12-18 16:26:36 - 上传线程结束: /mnt/save/warning/alarm_20251218_162634.jpg: 成功! +2025-12-18 16:26:40 - 开始上传图片: /mnt/save/warning/alarm_20251218_162639.jpg +2025-12-18 16:26:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:26:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:26:41 +2025-12-18 16:26:42 - 数据库插入成功 +2025-12-18 16:26:42 - 上传线程结束: /mnt/save/warning/alarm_20251218_162639.jpg: 成功! +2025-12-18 16:26:50 - 开始上传图片: /mnt/save/warning/alarm_20251218_162649.jpg +2025-12-18 16:26:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:26:52 +2025-12-18 16:26:52 - 数据库插入成功 +2025-12-18 16:26:52 - 上传线程结束: /mnt/save/warning/alarm_20251218_162649.jpg: 成功! +2025-12-18 16:27:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:27:14 - 开始上传图片: /mnt/save/warning/alarm_20251218_162714.jpg +2025-12-18 16:27:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:27:15 +2025-12-18 16:27:16 - 数据库插入成功 +2025-12-18 16:27:16 - 上传线程结束: /mnt/save/warning/alarm_20251218_162714.jpg: 成功! +2025-12-18 16:27:28 - 开始上传图片: /mnt/save/warning/alarm_20251218_162727.jpg +2025-12-18 16:27:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:27:29 +2025-12-18 16:27:30 - 数据库插入成功 +2025-12-18 16:27:30 - 上传线程结束: /mnt/save/warning/alarm_20251218_162727.jpg: 成功! +2025-12-18 16:27:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:28:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:28:22 - 开始上传图片: /mnt/save/warning/alarm_20251218_162822.jpg +2025-12-18 16:28:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:28:24 +2025-12-18 16:28:24 - 数据库插入成功 +2025-12-18 16:28:24 - 上传线程结束: /mnt/save/warning/alarm_20251218_162822.jpg: 成功! +2025-12-18 16:28:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:29:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:29:37 - 开始上传图片: /mnt/save/warning/alarm_20251218_162936.jpg +2025-12-18 16:29:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:29:38 +2025-12-18 16:29:38 - 数据库插入成功 +2025-12-18 16:29:38 - 上传线程结束: /mnt/save/warning/alarm_20251218_162936.jpg: 成功! +2025-12-18 16:29:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:30:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:30:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:31:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:31:17 - 开始上传图片: /mnt/save/warning/alarm_20251218_163116.jpg +2025-12-18 16:31:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:31:18 +2025-12-18 16:31:19 - 数据库插入成功 +2025-12-18 16:31:19 - 上传线程结束: /mnt/save/warning/alarm_20251218_163116.jpg: 成功! +2025-12-18 16:31:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:32:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:32:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:32:49 - 开始上传图片: /mnt/save/warning/alarm_20251218_163248.jpg +2025-12-18 16:32:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:32:50 +2025-12-18 16:32:51 - 数据库插入成功 +2025-12-18 16:32:51 - 上传线程结束: /mnt/save/warning/alarm_20251218_163248.jpg: 成功! +2025-12-18 16:33:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:33:41 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:33:57 - 开始上传图片: /mnt/save/warning/alarm_20251218_163356.jpg +2025-12-18 16:33:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:33:58 +2025-12-18 16:33:59 - 数据库插入成功 +2025-12-18 16:33:59 - 上传线程结束: /mnt/save/warning/alarm_20251218_163356.jpg: 成功! +2025-12-18 16:34:11 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:34:41 - 开始上传图片: /mnt/save/warning/alarm_20251218_163441.jpg +2025-12-18 16:34:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:34:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:34:42 +2025-12-18 16:34:43 - 数据库插入成功 +2025-12-18 16:34:43 - 上传线程结束: /mnt/save/warning/alarm_20251218_163441.jpg: 成功! +2025-12-18 16:34:45 - 开始上传图片: /mnt/save/warning/alarm_20251218_163444.jpg +2025-12-18 16:34:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:34:46 +2025-12-18 16:34:47 - 数据库插入成功 +2025-12-18 16:34:47 - 上传线程结束: /mnt/save/warning/alarm_20251218_163444.jpg: 成功! +2025-12-18 16:34:55 - 开始上传图片: /mnt/save/warning/alarm_20251218_163454.jpg +2025-12-18 16:34:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:34:56 +2025-12-18 16:34:57 - 数据库插入成功 +2025-12-18 16:34:57 - 上传线程结束: /mnt/save/warning/alarm_20251218_163454.jpg: 成功! +2025-12-18 16:35:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:35:19 - 开始上传图片: /mnt/save/warning/alarm_20251218_163519.jpg +2025-12-18 16:35:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:35:20 +2025-12-18 16:35:21 - 数据库插入成功 +2025-12-18 16:35:21 - 上传线程结束: /mnt/save/warning/alarm_20251218_163519.jpg: 成功! +2025-12-18 16:35:33 - 开始上传图片: /mnt/save/warning/alarm_20251218_163533.jpg +2025-12-18 16:35:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:35:34 +2025-12-18 16:35:35 - 数据库插入成功 +2025-12-18 16:35:35 - 上传线程结束: /mnt/save/warning/alarm_20251218_163533.jpg: 成功! +2025-12-18 16:35:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:36:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:36:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:37:03 - 开始上传图片: /mnt/save/warning/alarm_20251218_163702.jpg +2025-12-18 16:37:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:37:04 +2025-12-18 16:37:05 - 数据库插入成功 +2025-12-18 16:37:05 - 上传线程结束: /mnt/save/warning/alarm_20251218_163702.jpg: 成功! +2025-12-18 16:37:05 - 开始上传图片: /mnt/save/warning/alarm_20251218_163705.jpg +2025-12-18 16:37:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:37:06 +2025-12-18 16:37:07 - 数据库插入成功 +2025-12-18 16:37:07 - 上传线程结束: /mnt/save/warning/alarm_20251218_163705.jpg: 成功! +2025-12-18 16:37:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:37:17 - 开始上传图片: /mnt/save/warning/alarm_20251218_163717.jpg +2025-12-18 16:37:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:37:19 +2025-12-18 16:37:19 - 数据库插入成功 +2025-12-18 16:37:19 - 上传线程结束: /mnt/save/warning/alarm_20251218_163717.jpg: 成功! +2025-12-18 16:37:23 - 开始上传图片: /mnt/save/warning/alarm_20251218_163723.jpg +2025-12-18 16:37:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:37:25 +2025-12-18 16:37:25 - 数据库插入成功 +2025-12-18 16:37:25 - 上传线程结束: /mnt/save/warning/alarm_20251218_163723.jpg: 成功! +2025-12-18 16:37:32 - 开始上传图片: /mnt/save/warning/alarm_20251218_163731.jpg +2025-12-18 16:37:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:37:33 +2025-12-18 16:37:33 - 数据库插入成功 +2025-12-18 16:37:33 - 上传线程结束: /mnt/save/warning/alarm_20251218_163731.jpg: 成功! +2025-12-18 16:37:36 - 开始上传图片: /mnt/save/warning/alarm_20251218_163735.jpg +2025-12-18 16:37:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:37:37 +2025-12-18 16:37:37 - 数据库插入成功 +2025-12-18 16:37:37 - 上传线程结束: /mnt/save/warning/alarm_20251218_163735.jpg: 成功! +2025-12-18 16:37:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:37:44 - 开始上传图片: /mnt/save/warning/alarm_20251218_163743.jpg +2025-12-18 16:37:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:37:45 +2025-12-18 16:37:45 - 数据库插入成功 +2025-12-18 16:37:45 - 上传线程结束: /mnt/save/warning/alarm_20251218_163743.jpg: 成功! +2025-12-18 16:37:54 - 开始上传图片: /mnt/save/warning/alarm_20251218_163754.jpg +2025-12-18 16:37:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:37:55 +2025-12-18 16:37:55 - 数据库插入成功 +2025-12-18 16:37:55 - 上传线程结束: /mnt/save/warning/alarm_20251218_163754.jpg: 成功! +2025-12-18 16:37:58 - 开始上传图片: /mnt/save/warning/alarm_20251218_163757.jpg +2025-12-18 16:37:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:37:59 +2025-12-18 16:37:59 - 数据库插入成功 +2025-12-18 16:37:59 - 上传线程结束: /mnt/save/warning/alarm_20251218_163757.jpg: 成功! +2025-12-18 16:38:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:38:20 - 开始上传图片: /mnt/save/warning/alarm_20251218_163819.jpg +2025-12-18 16:38:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:38:21 +2025-12-18 16:38:21 - 数据库插入成功 +2025-12-18 16:38:21 - 上传线程结束: /mnt/save/warning/alarm_20251218_163819.jpg: 成功! +2025-12-18 16:38:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:38:54 - 开始上传图片: /mnt/save/warning/alarm_20251218_163853.jpg +2025-12-18 16:38:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:38:55 +2025-12-18 16:38:56 - 数据库插入成功 +2025-12-18 16:38:56 - 上传线程结束: /mnt/save/warning/alarm_20251218_163853.jpg: 成功! +2025-12-18 16:38:58 - 开始上传图片: /mnt/save/warning/alarm_20251218_163858.jpg +2025-12-18 16:38:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:38:59 +2025-12-18 16:39:00 - 数据库插入成功 +2025-12-18 16:39:00 - 上传线程结束: /mnt/save/warning/alarm_20251218_163858.jpg: 成功! +2025-12-18 16:39:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:39:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:39:46 - 开始上传图片: /mnt/save/warning/alarm_20251218_163946.jpg +2025-12-18 16:39:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:39:47 +2025-12-18 16:39:48 - 数据库插入成功 +2025-12-18 16:39:48 - 上传线程结束: /mnt/save/warning/alarm_20251218_163946.jpg: 成功! +2025-12-18 16:40:10 - 开始上传图片: /mnt/save/warning/alarm_20251218_164009.jpg +2025-12-18 16:40:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:40:11 +2025-12-18 16:40:12 - 数据库插入成功 +2025-12-18 16:40:12 - 上传线程结束: /mnt/save/warning/alarm_20251218_164009.jpg: 成功! +2025-12-18 16:40:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:40:24 - 开始上传图片: /mnt/save/warning/alarm_20251218_164024.jpg +2025-12-18 16:40:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:40:25 +2025-12-18 16:40:26 - 数据库插入成功 +2025-12-18 16:40:26 - 上传线程结束: /mnt/save/warning/alarm_20251218_164024.jpg: 成功! +2025-12-18 16:40:28 - 开始上传图片: /mnt/save/warning/alarm_20251218_164026.jpg +2025-12-18 16:40:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:40:29 +2025-12-18 16:40:30 - 数据库插入成功 +2025-12-18 16:40:30 - 上传线程结束: /mnt/save/warning/alarm_20251218_164026.jpg: 成功! +2025-12-18 16:40:30 - 开始上传图片: /mnt/save/warning/alarm_20251218_164028.jpg +2025-12-18 16:40:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:40:31 +2025-12-18 16:40:32 - 数据库插入成功 +2025-12-18 16:40:32 - 上传线程结束: /mnt/save/warning/alarm_20251218_164028.jpg: 成功! +2025-12-18 16:40:40 - 开始上传图片: /mnt/save/warning/alarm_20251218_164039.jpg +2025-12-18 16:40:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:40:41 +2025-12-18 16:40:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:40:42 - 数据库插入成功 +2025-12-18 16:40:42 - 上传线程结束: /mnt/save/warning/alarm_20251218_164039.jpg: 成功! +2025-12-18 16:40:50 - 开始上传图片: /mnt/save/warning/alarm_20251218_164049.jpg +2025-12-18 16:40:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:40:51 +2025-12-18 16:40:52 - 数据库插入成功 +2025-12-18 16:40:52 - 上传线程结束: /mnt/save/warning/alarm_20251218_164049.jpg: 成功! +2025-12-18 16:40:54 - 开始上传图片: /mnt/save/warning/alarm_20251218_164053.jpg +2025-12-18 16:40:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:40:55 +2025-12-18 16:40:56 - 数据库插入成功 +2025-12-18 16:40:56 - 上传线程结束: /mnt/save/warning/alarm_20251218_164053.jpg: 成功! +2025-12-18 16:40:58 - 开始上传图片: /mnt/save/warning/alarm_20251218_164058.jpg +2025-12-18 16:40:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:40:59 +2025-12-18 16:41:00 - 数据库插入成功 +2025-12-18 16:41:00 - 上传线程结束: /mnt/save/warning/alarm_20251218_164058.jpg: 成功! +2025-12-18 16:41:10 - 开始上传图片: /mnt/save/warning/alarm_20251218_164109.jpg +2025-12-18 16:41:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:41:11 +2025-12-18 16:41:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:41:12 - 数据库插入成功 +2025-12-18 16:41:12 - 上传线程结束: /mnt/save/warning/alarm_20251218_164109.jpg: 成功! +2025-12-18 16:41:20 - 开始上传图片: /mnt/save/warning/alarm_20251218_164118.jpg +2025-12-18 16:41:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:41:21 +2025-12-18 16:41:22 - 数据库插入成功 +2025-12-18 16:41:22 - 上传线程结束: /mnt/save/warning/alarm_20251218_164118.jpg: 成功! +2025-12-18 16:41:28 - 开始上传图片: /mnt/save/warning/alarm_20251218_164128.jpg +2025-12-18 16:41:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:41:29 +2025-12-18 16:41:30 - 数据库插入成功 +2025-12-18 16:41:30 - 上传线程结束: /mnt/save/warning/alarm_20251218_164128.jpg: 成功! +2025-12-18 16:41:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:42:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:42:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:42:46 - 开始上传图片: /mnt/save/warning/alarm_20251218_164246.jpg +2025-12-18 16:42:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:42:48 +2025-12-18 16:42:48 - 数据库插入成功 +2025-12-18 16:42:48 - 上传线程结束: /mnt/save/warning/alarm_20251218_164246.jpg: 成功! +2025-12-18 16:42:55 - 开始上传图片: /mnt/save/warning/alarm_20251218_164254.jpg +2025-12-18 16:42:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:42:56 +2025-12-18 16:42:56 - 数据库插入成功 +2025-12-18 16:42:56 - 上传线程结束: /mnt/save/warning/alarm_20251218_164254.jpg: 成功! +2025-12-18 16:43:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:43:23 - 开始上传图片: /mnt/save/warning/alarm_20251218_164321.jpg +2025-12-18 16:43:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:43:24 +2025-12-18 16:43:25 - 数据库插入成功 +2025-12-18 16:43:25 - 上传线程结束: /mnt/save/warning/alarm_20251218_164321.jpg: 成功! +2025-12-18 16:43:27 - 开始上传图片: /mnt/save/warning/alarm_20251218_164327.jpg +2025-12-18 16:43:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:43:28 +2025-12-18 16:43:29 - 数据库插入成功 +2025-12-18 16:43:29 - 上传线程结束: /mnt/save/warning/alarm_20251218_164327.jpg: 成功! +2025-12-18 16:43:35 - 开始上传图片: /mnt/save/warning/alarm_20251218_164334.jpg +2025-12-18 16:43:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:43:36 +2025-12-18 16:43:37 - 数据库插入成功 +2025-12-18 16:43:37 - 上传线程结束: /mnt/save/warning/alarm_20251218_164334.jpg: 成功! +2025-12-18 16:43:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:43:47 - 开始上传图片: /mnt/save/warning/alarm_20251218_164345.jpg +2025-12-18 16:43:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:43:48 +2025-12-18 16:43:48 - 数据库插入成功 +2025-12-18 16:43:48 - 上传线程结束: /mnt/save/warning/alarm_20251218_164345.jpg: 成功! +2025-12-18 16:44:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:44:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:45:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:45:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:46:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:46:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:47:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:47:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:48:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:48:17 - 开始上传图片: /mnt/save/warning/alarm_20251218_164815.jpg +2025-12-18 16:48:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:48:19 +2025-12-18 16:48:19 - 数据库插入成功 +2025-12-18 16:48:19 - 上传线程结束: /mnt/save/warning/alarm_20251218_164815.jpg: 成功! +2025-12-18 16:48:27 - 开始上传图片: /mnt/save/warning/alarm_20251218_164827.jpg +2025-12-18 16:48:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:48:29 +2025-12-18 16:48:29 - 数据库插入成功 +2025-12-18 16:48:29 - 上传线程结束: /mnt/save/warning/alarm_20251218_164827.jpg: 成功! +2025-12-18 16:48:31 - 开始上传图片: /mnt/save/warning/alarm_20251218_164830.jpg +2025-12-18 16:48:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:48:33 +2025-12-18 16:48:33 - 数据库插入成功 +2025-12-18 16:48:33 - 上传线程结束: /mnt/save/warning/alarm_20251218_164830.jpg: 成功! +2025-12-18 16:48:35 - 开始上传图片: /mnt/save/warning/alarm_20251218_164834.jpg +2025-12-18 16:48:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 16:48:37 +2025-12-18 16:48:37 - 数据库插入成功 +2025-12-18 16:48:37 - 上传线程结束: /mnt/save/warning/alarm_20251218_164834.jpg: 成功! +2025-12-18 16:48:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:49:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:49:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:50:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:50:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:51:12 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:51:42 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:52:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:52:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:53:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:53:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:54:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:54:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:55:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:55:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:56:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:56:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:57:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:57:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:58:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:58:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:59:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 16:59:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:00:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:00:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:01:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:01:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:02:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:02:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:03:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:03:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:04:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:04:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:04:50 - 开始上传图片: /mnt/save/warning/alarm_20251218_170449.jpg +2025-12-18 17:04:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 17:04:51 +2025-12-18 17:04:52 - 数据库插入成功 +2025-12-18 17:04:52 - 上传线程结束: /mnt/save/warning/alarm_20251218_170449.jpg: 成功! +2025-12-18 17:05:08 - 开始上传图片: /mnt/save/warning/alarm_20251218_170507.jpg +2025-12-18 17:05:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 17:05:09 +2025-12-18 17:05:10 - 数据库插入成功 +2025-12-18 17:05:10 - 上传线程结束: /mnt/save/warning/alarm_20251218_170507.jpg: 成功! +2025-12-18 17:05:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:05:32 - 开始上传图片: /mnt/save/warning/alarm_20251218_170531.jpg +2025-12-18 17:05:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 17:05:33 +2025-12-18 17:05:34 - 数据库插入成功 +2025-12-18 17:05:34 - 上传线程结束: /mnt/save/warning/alarm_20251218_170531.jpg: 成功! +2025-12-18 17:05:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:06:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:06:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:07:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:07:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:08:13 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:08:43 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:09:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:09:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:10:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:10:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:11:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:11:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:12:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:12:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:13:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:13:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:14:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:14:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:15:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:15:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:16:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:16:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:17:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:17:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:18:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:18:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:19:06 - 开始上传图片: /mnt/save/warning/alarm_20251218_171904.jpg +2025-12-18 17:19:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-18 17:19:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-18 17:19:07 +2025-12-18 17:19:07 - 数据库插入成功 +2025-12-18 17:19:07 - 上传线程结束: /mnt/save/warning/alarm_20251218_171904.jpg: 成功! +2025-12-18 17:19:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:19:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-18 17:20:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:18:09 - ICCID刷新线程启动 +2025-12-19 09:18:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 09:18:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:19:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:19:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:20:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:20:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:21:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:21:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:22:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:22:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:23:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:23:46 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:24:16 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:24:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:25:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:25:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:26:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:26:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:27:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:27:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:28:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:28:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:29:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:29:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:30:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:30:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:31:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:31:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:32:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:32:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:33:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:33:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:34:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:34:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:35:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:35:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:36:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:36:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:37:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:37:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:38:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:38:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:39:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:39:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:40:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:40:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:41:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:41:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:42:17 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:42:47 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:43:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:43:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:44:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:44:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:45:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:45:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:46:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:46:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:47:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:47:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:48:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:48:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:49:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:49:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:50:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:50:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:51:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:51:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:52:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:52:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:53:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:53:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:54:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:54:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:55:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:55:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:56:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:56:48 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:57:06 - 开始上传图片: /mnt/save/warning/alarm_20251219_095705.jpg +2025-12-19 09:57:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 09:57:08 +2025-12-19 09:57:08 - 数据库插入成功 +2025-12-19 09:57:08 - 上传线程结束: /mnt/save/warning/alarm_20251219_095705.jpg: 成功! +2025-12-19 09:57:18 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 09:58:19 - ICCID刷新线程启动 +2025-12-19 09:58:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 09:59:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 09:59:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:00:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:00:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:01:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:01:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:02:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:02:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:03:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:03:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:04:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:04:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:05:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:05:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:06:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:06:21 - 开始上传图片: /mnt/save/warning/alarm_20251219_100620.jpg +2025-12-19 10:06:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:06:22 +2025-12-19 10:06:23 - 数据库插入成功 +2025-12-19 10:06:23 - 上传线程结束: /mnt/save/warning/alarm_20251219_100620.jpg: 成功! +2025-12-19 10:06:29 - 开始上传图片: /mnt/save/warning/alarm_20251219_100629.jpg +2025-12-19 10:06:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:06:30 +2025-12-19 10:06:31 - 数据库插入成功 +2025-12-19 10:06:31 - 上传线程结束: /mnt/save/warning/alarm_20251219_100629.jpg: 成功! +2025-12-19 10:06:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:06:35 - 开始上传图片: /mnt/save/warning/alarm_20251219_100634.jpg +2025-12-19 10:06:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:06:36 +2025-12-19 10:06:37 - 数据库插入成功 +2025-12-19 10:06:37 - 上传线程结束: /mnt/save/warning/alarm_20251219_100634.jpg: 成功! +2025-12-19 10:06:37 - 开始上传图片: /mnt/save/warning/alarm_20251219_100637.jpg +2025-12-19 10:06:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:06:38 +2025-12-19 10:06:39 - 数据库插入成功 +2025-12-19 10:06:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_100637.jpg: 成功! +2025-12-19 10:06:41 - 开始上传图片: /mnt/save/warning/alarm_20251219_100639.jpg +2025-12-19 10:06:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:06:42 +2025-12-19 10:06:43 - 数据库插入成功 +2025-12-19 10:06:43 - 上传线程结束: /mnt/save/warning/alarm_20251219_100639.jpg: 成功! +2025-12-19 10:06:43 - 开始上传图片: /mnt/save/warning/alarm_20251219_100642.jpg +2025-12-19 10:06:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:06:44 +2025-12-19 10:06:45 - 数据库插入成功 +2025-12-19 10:06:45 - 上传线程结束: /mnt/save/warning/alarm_20251219_100642.jpg: 成功! +2025-12-19 10:06:49 - 开始上传图片: /mnt/save/warning/alarm_20251219_100647.jpg +2025-12-19 10:06:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:06:50 +2025-12-19 10:06:51 - 数据库插入成功 +2025-12-19 10:06:51 - 上传线程结束: /mnt/save/warning/alarm_20251219_100647.jpg: 成功! +2025-12-19 10:06:53 - 开始上传图片: /mnt/save/warning/alarm_20251219_100652.jpg +2025-12-19 10:06:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:06:54 +2025-12-19 10:06:55 - 数据库插入成功 +2025-12-19 10:06:55 - 上传线程结束: /mnt/save/warning/alarm_20251219_100652.jpg: 成功! +2025-12-19 10:07:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:07:33 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:07:37 - 开始上传图片: /mnt/save/warning/alarm_20251219_100735.jpg +2025-12-19 10:07:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:07:38 +2025-12-19 10:07:39 - 开始上传图片: /mnt/save/warning/alarm_20251219_100739.jpg +2025-12-19 10:07:39 - 数据库插入成功 +2025-12-19 10:07:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_100735.jpg: 成功! +2025-12-19 10:07:40 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:07:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:07:40 +2025-12-19 10:07:40 - 数据库插入成功 +2025-12-19 10:07:40 - 上传线程结束: /mnt/save/warning/alarm_20251219_100739.jpg: 成功! +2025-12-19 10:08:03 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:08:07 - 开始上传图片: /mnt/save/warning/alarm_20251219_100806.jpg +2025-12-19 10:08:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:08:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:08:07 +2025-12-19 10:08:09 - 数据库插入成功 +2025-12-19 10:08:09 - 上传线程结束: /mnt/save/warning/alarm_20251219_100806.jpg: 成功! +2025-12-19 10:08:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:09:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:09:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:10:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:10:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:10:54 - 开始上传图片: /mnt/save/warning/alarm_20251219_101053.jpg +2025-12-19 10:10:54 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:10:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:10:54 +2025-12-19 10:10:54 - 数据库插入成功 +2025-12-19 10:10:54 - 上传线程结束: /mnt/save/warning/alarm_20251219_101053.jpg: 成功! +2025-12-19 10:11:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:11:18 - 开始上传图片: /mnt/save/warning/alarm_20251219_101116.jpg +2025-12-19 10:11:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:11:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:11:18 +2025-12-19 10:11:18 - 数据库插入成功 +2025-12-19 10:11:18 - 上传线程结束: /mnt/save/warning/alarm_20251219_101116.jpg: 成功! +2025-12-19 10:11:28 - 开始上传图片: /mnt/save/warning/alarm_20251219_101126.jpg +2025-12-19 10:11:28 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:11:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:11:28 +2025-12-19 10:11:29 - 数据库插入成功 +2025-12-19 10:11:29 - 上传线程结束: /mnt/save/warning/alarm_20251219_101126.jpg: 成功! +2025-12-19 10:11:32 - 开始上传图片: /mnt/save/warning/alarm_20251219_101131.jpg +2025-12-19 10:11:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:11:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:11:32 +2025-12-19 10:11:32 - 数据库插入成功 +2025-12-19 10:11:32 - 上传线程结束: /mnt/save/warning/alarm_20251219_101131.jpg: 成功! +2025-12-19 10:11:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:11:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_101137.jpg +2025-12-19 10:11:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:11:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:11:38 +2025-12-19 10:11:39 - 数据库插入成功 +2025-12-19 10:11:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_101137.jpg: 成功! +2025-12-19 10:12:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:12:04 - 开始上传图片: /mnt/save/warning/alarm_20251219_101203.jpg +2025-12-19 10:12:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:12:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:12:04 +2025-12-19 10:12:05 - 数据库插入成功 +2025-12-19 10:12:05 - 上传线程结束: /mnt/save/warning/alarm_20251219_101203.jpg: 成功! +2025-12-19 10:12:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_101206.jpg +2025-12-19 10:12:08 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:12:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:12:08 +2025-12-19 10:12:08 - 数据库插入成功 +2025-12-19 10:12:08 - 上传线程结束: /mnt/save/warning/alarm_20251219_101206.jpg: 成功! +2025-12-19 10:12:10 - 开始上传图片: /mnt/save/warning/alarm_20251219_101209.jpg +2025-12-19 10:12:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:12:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:12:10 +2025-12-19 10:12:10 - 数据库插入成功 +2025-12-19 10:12:10 - 上传线程结束: /mnt/save/warning/alarm_20251219_101209.jpg: 成功! +2025-12-19 10:12:14 - 开始上传图片: /mnt/save/warning/alarm_20251219_101213.jpg +2025-12-19 10:12:14 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:12:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:12:14 +2025-12-19 10:12:15 - 数据库插入成功 +2025-12-19 10:12:15 - 上传线程结束: /mnt/save/warning/alarm_20251219_101213.jpg: 成功! +2025-12-19 10:12:30 - 开始上传图片: /mnt/save/warning/alarm_20251219_101230.jpg +2025-12-19 10:12:30 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:12:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:12:30 +2025-12-19 10:12:31 - 数据库插入成功 +2025-12-19 10:12:31 - 上传线程结束: /mnt/save/warning/alarm_20251219_101230.jpg: 成功! +2025-12-19 10:12:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:12:34 - 开始上传图片: /mnt/save/warning/alarm_20251219_101232.jpg +2025-12-19 10:12:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:12:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:12:34 +2025-12-19 10:12:35 - 数据库插入成功 +2025-12-19 10:12:35 - 上传线程结束: /mnt/save/warning/alarm_20251219_101232.jpg: 成功! +2025-12-19 10:13:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:13:10 - 开始上传图片: /mnt/save/warning/alarm_20251219_101309.jpg +2025-12-19 10:13:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:13:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:13:10 +2025-12-19 10:13:11 - 数据库插入成功 +2025-12-19 10:13:11 - 上传线程结束: /mnt/save/warning/alarm_20251219_101309.jpg: 成功! +2025-12-19 10:13:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:13:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_101336.jpg +2025-12-19 10:13:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:13:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:13:38 +2025-12-19 10:13:39 - 数据库插入成功 +2025-12-19 10:13:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_101336.jpg: 成功! +2025-12-19 10:13:48 - 开始上传图片: /mnt/save/warning/alarm_20251219_101347.jpg +2025-12-19 10:13:48 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:13:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:13:48 +2025-12-19 10:13:49 - 数据库插入成功 +2025-12-19 10:13:49 - 上传线程结束: /mnt/save/warning/alarm_20251219_101347.jpg: 成功! +2025-12-19 10:13:52 - 开始上传图片: /mnt/save/warning/alarm_20251219_101350.jpg +2025-12-19 10:13:52 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:13:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:13:52 +2025-12-19 10:13:53 - 数据库插入成功 +2025-12-19 10:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_101350.jpg: 成功! +2025-12-19 10:14:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:14:04 - 开始上传图片: /mnt/save/warning/alarm_20251219_101402.jpg +2025-12-19 10:14:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:14:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:14:04 +2025-12-19 10:14:05 - 数据库插入成功 +2025-12-19 10:14:05 - 上传线程结束: /mnt/save/warning/alarm_20251219_101402.jpg: 成功! +2025-12-19 10:14:32 - 开始上传图片: /mnt/save/warning/alarm_20251219_101432.jpg +2025-12-19 10:14:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:14:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:14:32 +2025-12-19 10:14:33 - 数据库插入成功 +2025-12-19 10:14:33 - 上传线程结束: /mnt/save/warning/alarm_20251219_101432.jpg: 成功! +2025-12-19 10:14:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:14:36 - 开始上传图片: /mnt/save/warning/alarm_20251219_101436.jpg +2025-12-19 10:14:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:14:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:14:36 +2025-12-19 10:14:37 - 数据库插入成功 +2025-12-19 10:14:37 - 上传线程结束: /mnt/save/warning/alarm_20251219_101436.jpg: 成功! +2025-12-19 10:14:42 - 开始上传图片: /mnt/save/warning/alarm_20251219_101441.jpg +2025-12-19 10:14:42 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:14:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:14:42 +2025-12-19 10:14:43 - 数据库插入成功 +2025-12-19 10:14:43 - 上传线程结束: /mnt/save/warning/alarm_20251219_101441.jpg: 成功! +2025-12-19 10:14:54 - 开始上传图片: /mnt/save/warning/alarm_20251219_101453.jpg +2025-12-19 10:14:54 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:14:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:14:54 +2025-12-19 10:14:55 - 数据库插入成功 +2025-12-19 10:14:55 - 上传线程结束: /mnt/save/warning/alarm_20251219_101453.jpg: 成功! +2025-12-19 10:14:58 - 开始上传图片: /mnt/save/warning/alarm_20251219_101457.jpg +2025-12-19 10:14:58 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:14:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:14:58 +2025-12-19 10:14:59 - 数据库插入成功 +2025-12-19 10:14:59 - 上传线程结束: /mnt/save/warning/alarm_20251219_101457.jpg: 成功! +2025-12-19 10:15:02 - 开始上传图片: /mnt/save/warning/alarm_20251219_101502.jpg +2025-12-19 10:15:02 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:15:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:15:02 +2025-12-19 10:15:03 - 数据库插入成功 +2025-12-19 10:15:03 - 上传线程结束: /mnt/save/warning/alarm_20251219_101502.jpg: 成功! +2025-12-19 10:15:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:15:10 - 开始上传图片: /mnt/save/warning/alarm_20251219_101510.jpg +2025-12-19 10:15:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:15:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:15:10 +2025-12-19 10:15:11 - 数据库插入成功 +2025-12-19 10:15:11 - 上传线程结束: /mnt/save/warning/alarm_20251219_101510.jpg: 成功! +2025-12-19 10:15:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:15:36 - 开始上传图片: /mnt/save/warning/alarm_20251219_101534.jpg +2025-12-19 10:15:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:15:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:15:36 +2025-12-19 10:15:37 - 数据库插入成功 +2025-12-19 10:15:37 - 上传线程结束: /mnt/save/warning/alarm_20251219_101534.jpg: 成功! +2025-12-19 10:15:44 - 开始上传图片: /mnt/save/warning/alarm_20251219_101543.jpg +2025-12-19 10:15:44 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:15:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:15:44 +2025-12-19 10:15:45 - 数据库插入成功 +2025-12-19 10:15:45 - 上传线程结束: /mnt/save/warning/alarm_20251219_101543.jpg: 成功! +2025-12-19 10:15:46 - 开始上传图片: /mnt/save/warning/alarm_20251219_101546.jpg +2025-12-19 10:15:46 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:15:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:15:46 +2025-12-19 10:15:47 - 数据库插入成功 +2025-12-19 10:15:47 - 上传线程结束: /mnt/save/warning/alarm_20251219_101546.jpg: 成功! +2025-12-19 10:15:54 - 开始上传图片: /mnt/save/warning/alarm_20251219_101553.jpg +2025-12-19 10:15:54 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:15:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:15:54 +2025-12-19 10:15:55 - 数据库插入成功 +2025-12-19 10:15:55 - 上传线程结束: /mnt/save/warning/alarm_20251219_101553.jpg: 成功! +2025-12-19 10:16:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:16:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:17:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:17:23 - 开始上传图片: /mnt/save/warning/alarm_20251219_101721.jpg +2025-12-19 10:17:23 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:17:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:17:23 +2025-12-19 10:17:24 - 数据库插入成功 +2025-12-19 10:17:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_101721.jpg: 成功! +2025-12-19 10:17:33 - 开始上传图片: /mnt/save/warning/alarm_20251219_101732.jpg +2025-12-19 10:17:33 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:17:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:17:33 +2025-12-19 10:17:34 - 数据库插入成功 +2025-12-19 10:17:34 - 上传线程结束: /mnt/save/warning/alarm_20251219_101732.jpg: 成功! +2025-12-19 10:17:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:17:37 - 开始上传图片: /mnt/save/warning/alarm_20251219_101736.jpg +2025-12-19 10:17:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:17:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:17:37 +2025-12-19 10:17:37 - 数据库插入成功 +2025-12-19 10:17:37 - 上传线程结束: /mnt/save/warning/alarm_20251219_101736.jpg: 成功! +2025-12-19 10:18:01 - 开始上传图片: /mnt/save/warning/alarm_20251219_101759.jpg +2025-12-19 10:18:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:18:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:18:01 +2025-12-19 10:18:01 - 数据库插入成功 +2025-12-19 10:18:01 - 上传线程结束: /mnt/save/warning/alarm_20251219_101759.jpg: 成功! +2025-12-19 10:18:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:18:13 - 开始上传图片: /mnt/save/warning/alarm_20251219_101811.jpg +2025-12-19 10:18:13 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:18:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:18:13 +2025-12-19 10:18:14 - 数据库插入成功 +2025-12-19 10:18:14 - 上传线程结束: /mnt/save/warning/alarm_20251219_101811.jpg: 成功! +2025-12-19 10:18:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:18:39 - 开始上传图片: /mnt/save/warning/alarm_20251219_101838.jpg +2025-12-19 10:18:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:18:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:18:39 +2025-12-19 10:18:40 - 数据库插入成功 +2025-12-19 10:18:40 - 上传线程结束: /mnt/save/warning/alarm_20251219_101838.jpg: 成功! +2025-12-19 10:18:45 - 开始上传图片: /mnt/save/warning/alarm_20251219_101844.jpg +2025-12-19 10:18:45 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:18:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:18:45 +2025-12-19 10:18:46 - 数据库插入成功 +2025-12-19 10:18:46 - 上传线程结束: /mnt/save/warning/alarm_20251219_101844.jpg: 成功! +2025-12-19 10:18:47 - 开始上传图片: /mnt/save/warning/alarm_20251219_101847.jpg +2025-12-19 10:18:47 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:18:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:18:47 +2025-12-19 10:18:48 - 数据库插入成功 +2025-12-19 10:18:48 - 上传线程结束: /mnt/save/warning/alarm_20251219_101847.jpg: 成功! +2025-12-19 10:18:53 - 开始上传图片: /mnt/save/warning/alarm_20251219_101851.jpg +2025-12-19 10:18:53 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:18:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:18:53 +2025-12-19 10:18:54 - 数据库插入成功 +2025-12-19 10:18:54 - 上传线程结束: /mnt/save/warning/alarm_20251219_101851.jpg: 成功! +2025-12-19 10:19:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:19:05 - 开始上传图片: /mnt/save/warning/alarm_20251219_101903.jpg +2025-12-19 10:19:05 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:19:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:19:05 +2025-12-19 10:19:06 - 数据库插入成功 +2025-12-19 10:19:06 - 上传线程结束: /mnt/save/warning/alarm_20251219_101903.jpg: 成功! +2025-12-19 10:19:27 - 开始上传图片: /mnt/save/warning/alarm_20251219_101927.jpg +2025-12-19 10:19:27 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:19:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:19:27 +2025-12-19 10:19:28 - 数据库插入成功 +2025-12-19 10:19:28 - 上传线程结束: /mnt/save/warning/alarm_20251219_101927.jpg: 成功! +2025-12-19 10:19:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:19:43 - 开始上传图片: /mnt/save/warning/alarm_20251219_101941.jpg +2025-12-19 10:19:43 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:19:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:19:43 +2025-12-19 10:19:44 - 数据库插入成功 +2025-12-19 10:19:44 - 上传线程结束: /mnt/save/warning/alarm_20251219_101941.jpg: 成功! +2025-12-19 10:19:45 - 开始上传图片: /mnt/save/warning/alarm_20251219_101944.jpg +2025-12-19 10:19:45 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:19:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:19:45 +2025-12-19 10:19:46 - 数据库插入成功 +2025-12-19 10:19:46 - 上传线程结束: /mnt/save/warning/alarm_20251219_101944.jpg: 成功! +2025-12-19 10:20:03 - 开始上传图片: /mnt/save/warning/alarm_20251219_102002.jpg +2025-12-19 10:20:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:20:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:20:03 +2025-12-19 10:20:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:20:04 - 数据库插入成功 +2025-12-19 10:20:04 - 上传线程结束: /mnt/save/warning/alarm_20251219_102002.jpg: 成功! +2025-12-19 10:20:15 - 开始上传图片: /mnt/save/warning/alarm_20251219_102013.jpg +2025-12-19 10:20:15 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:20:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:20:15 +2025-12-19 10:20:16 - 数据库插入成功 +2025-12-19 10:20:16 - 上传线程结束: /mnt/save/warning/alarm_20251219_102013.jpg: 成功! +2025-12-19 10:20:25 - 开始上传图片: /mnt/save/warning/alarm_20251219_102025.jpg +2025-12-19 10:20:25 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:20:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:20:25 +2025-12-19 10:20:26 - 数据库插入成功 +2025-12-19 10:20:26 - 上传线程结束: /mnt/save/warning/alarm_20251219_102025.jpg: 成功! +2025-12-19 10:20:29 - 开始上传图片: /mnt/save/warning/alarm_20251219_102028.jpg +2025-12-19 10:20:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:20:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:20:29 +2025-12-19 10:20:30 - 数据库插入成功 +2025-12-19 10:20:30 - 上传线程结束: /mnt/save/warning/alarm_20251219_102028.jpg: 成功! +2025-12-19 10:20:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:20:55 - 开始上传图片: /mnt/save/warning/alarm_20251219_102054.jpg +2025-12-19 10:20:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:20:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:20:55 +2025-12-19 10:20:56 - 数据库插入成功 +2025-12-19 10:20:56 - 上传线程结束: /mnt/save/warning/alarm_20251219_102054.jpg: 成功! +2025-12-19 10:21:01 - 开始上传图片: /mnt/save/warning/alarm_20251219_102101.jpg +2025-12-19 10:21:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:21:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:21:01 +2025-12-19 10:21:02 - 数据库插入成功 +2025-12-19 10:21:02 - 上传线程结束: /mnt/save/warning/alarm_20251219_102101.jpg: 成功! +2025-12-19 10:21:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:21:21 - 开始上传图片: /mnt/save/warning/alarm_20251219_102120.jpg +2025-12-19 10:21:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:21:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:21:21 +2025-12-19 10:21:22 - 数据库插入成功 +2025-12-19 10:21:22 - 上传线程结束: /mnt/save/warning/alarm_20251219_102120.jpg: 成功! +2025-12-19 10:21:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:21:35 - 开始上传图片: /mnt/save/warning/alarm_20251219_102133.jpg +2025-12-19 10:21:35 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:21:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:21:35 +2025-12-19 10:21:36 - 数据库插入成功 +2025-12-19 10:21:36 - 上传线程结束: /mnt/save/warning/alarm_20251219_102133.jpg: 成功! +2025-12-19 10:21:45 - 开始上传图片: /mnt/save/warning/alarm_20251219_102144.jpg +2025-12-19 10:21:45 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:21:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:21:45 +2025-12-19 10:21:46 - 数据库插入成功 +2025-12-19 10:21:46 - 上传线程结束: /mnt/save/warning/alarm_20251219_102144.jpg: 成功! +2025-12-19 10:22:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:22:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:23:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:23:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:23:52 - 开始上传图片: /mnt/save/warning/alarm_20251219_102350.jpg +2025-12-19 10:23:52 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:23:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:23:52 +2025-12-19 10:23:53 - 数据库插入成功 +2025-12-19 10:23:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_102350.jpg: 成功! +2025-12-19 10:24:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:24:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_102407.jpg +2025-12-19 10:24:08 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:24:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:24:08 +2025-12-19 10:24:09 - 数据库插入成功 +2025-12-19 10:24:09 - 上传线程结束: /mnt/save/warning/alarm_20251219_102407.jpg: 成功! +2025-12-19 10:24:20 - 开始上传图片: /mnt/save/warning/alarm_20251219_102419.jpg +2025-12-19 10:24:20 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:24:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:24:20 +2025-12-19 10:24:21 - 数据库插入成功 +2025-12-19 10:24:21 - 上传线程结束: /mnt/save/warning/alarm_20251219_102419.jpg: 成功! +2025-12-19 10:24:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:24:56 - 开始上传图片: /mnt/save/warning/alarm_20251219_102454.jpg +2025-12-19 10:24:56 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:24:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:24:56 +2025-12-19 10:24:57 - 数据库插入成功 +2025-12-19 10:24:57 - 上传线程结束: /mnt/save/warning/alarm_20251219_102454.jpg: 成功! +2025-12-19 10:25:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:25:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:25:56 - 开始上传图片: /mnt/save/warning/alarm_20251219_102556.jpg +2025-12-19 10:25:56 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:25:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:25:56 +2025-12-19 10:25:57 - 数据库插入成功 +2025-12-19 10:25:57 - 上传线程结束: /mnt/save/warning/alarm_20251219_102556.jpg: 成功! +2025-12-19 10:26:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:26:14 - 开始上传图片: /mnt/save/warning/alarm_20251219_102613.jpg +2025-12-19 10:26:14 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:26:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:26:14 +2025-12-19 10:26:15 - 数据库插入成功 +2025-12-19 10:26:15 - 上传线程结束: /mnt/save/warning/alarm_20251219_102613.jpg: 成功! +2025-12-19 10:26:24 - 开始上传图片: /mnt/save/warning/alarm_20251219_102624.jpg +2025-12-19 10:26:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:26:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:26:24 +2025-12-19 10:26:25 - 数据库插入成功 +2025-12-19 10:26:25 - 上传线程结束: /mnt/save/warning/alarm_20251219_102624.jpg: 成功! +2025-12-19 10:26:34 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:26:44 - 开始上传图片: /mnt/save/warning/alarm_20251219_102643.jpg +2025-12-19 10:26:44 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:26:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:26:44 +2025-12-19 10:26:45 - 数据库插入成功 +2025-12-19 10:26:45 - 上传线程结束: /mnt/save/warning/alarm_20251219_102643.jpg: 成功! +2025-12-19 10:27:00 - 开始上传图片: /mnt/save/warning/alarm_20251219_102659.jpg +2025-12-19 10:27:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:01 +2025-12-19 10:27:02 - 数据库插入成功 +2025-12-19 10:27:02 - 上传线程结束: /mnt/save/warning/alarm_20251219_102659.jpg: 成功! +2025-12-19 10:27:02 - 开始上传图片: /mnt/save/warning/alarm_20251219_102702.jpg +2025-12-19 10:27:02 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:02 +2025-12-19 10:27:03 - 数据库插入成功 +2025-12-19 10:27:03 - 上传线程结束: /mnt/save/warning/alarm_20251219_102702.jpg: 成功! +2025-12-19 10:27:04 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:27:14 - 开始上传图片: /mnt/save/warning/alarm_20251219_102714.jpg +2025-12-19 10:27:15 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:15 +2025-12-19 10:27:15 - 数据库插入成功 +2025-12-19 10:27:15 - 上传线程结束: /mnt/save/warning/alarm_20251219_102714.jpg: 成功! +2025-12-19 10:27:19 - 开始上传图片: /mnt/save/warning/alarm_20251219_102717.jpg +2025-12-19 10:27:19 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:19 +2025-12-19 10:27:20 - 数据库插入成功 +2025-12-19 10:27:20 - 上传线程结束: /mnt/save/warning/alarm_20251219_102717.jpg: 成功! +2025-12-19 10:27:23 - 开始上传图片: /mnt/save/warning/alarm_20251219_102722.jpg +2025-12-19 10:27:23 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:23 +2025-12-19 10:27:24 - 数据库插入成功 +2025-12-19 10:27:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_102722.jpg: 成功! +2025-12-19 10:27:31 - 开始上传图片: /mnt/save/warning/alarm_20251219_102729.jpg +2025-12-19 10:27:31 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:31 +2025-12-19 10:27:32 - 数据库插入成功 +2025-12-19 10:27:32 - 上传线程结束: /mnt/save/warning/alarm_20251219_102729.jpg: 成功! +2025-12-19 10:27:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:27:35 - 开始上传图片: /mnt/save/warning/alarm_20251219_102734.jpg +2025-12-19 10:27:35 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:35 +2025-12-19 10:27:36 - 数据库插入成功 +2025-12-19 10:27:36 - 上传线程结束: /mnt/save/warning/alarm_20251219_102734.jpg: 成功! +2025-12-19 10:27:39 - 开始上传图片: /mnt/save/warning/alarm_20251219_102739.jpg +2025-12-19 10:27:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:39 +2025-12-19 10:27:40 - 数据库插入成功 +2025-12-19 10:27:40 - 上传线程结束: /mnt/save/warning/alarm_20251219_102739.jpg: 成功! +2025-12-19 10:27:47 - 开始上传图片: /mnt/save/warning/alarm_20251219_102746.jpg +2025-12-19 10:27:47 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:47 +2025-12-19 10:27:48 - 数据库插入成功 +2025-12-19 10:27:48 - 上传线程结束: /mnt/save/warning/alarm_20251219_102746.jpg: 成功! +2025-12-19 10:27:55 - 开始上传图片: /mnt/save/warning/alarm_20251219_102753.jpg +2025-12-19 10:27:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:27:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:27:55 +2025-12-19 10:27:56 - 数据库插入成功 +2025-12-19 10:27:56 - 上传线程结束: /mnt/save/warning/alarm_20251219_102753.jpg: 成功! +2025-12-19 10:28:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:28:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:28:37 - 开始上传图片: /mnt/save/warning/alarm_20251219_102836.jpg +2025-12-19 10:28:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:28:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:28:37 +2025-12-19 10:28:38 - 数据库插入成功 +2025-12-19 10:28:38 - 上传线程结束: /mnt/save/warning/alarm_20251219_102836.jpg: 成功! +2025-12-19 10:29:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:29:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:30:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:30:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:31:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:31:09 - 开始上传图片: /mnt/save/warning/alarm_20251219_103109.jpg +2025-12-19 10:31:09 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:31:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:31:09 +2025-12-19 10:31:10 - 数据库插入成功 +2025-12-19 10:31:10 - 上传线程结束: /mnt/save/warning/alarm_20251219_103109.jpg: 成功! +2025-12-19 10:31:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:31:55 - 开始上传图片: /mnt/save/warning/alarm_20251219_103154.jpg +2025-12-19 10:31:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:31:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:31:55 +2025-12-19 10:31:57 - 数据库插入成功 +2025-12-19 10:31:57 - 上传线程结束: /mnt/save/warning/alarm_20251219_103154.jpg: 成功! +2025-12-19 10:32:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:32:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:32:35 - 开始上传图片: /mnt/save/warning/alarm_20251219_103235.jpg +2025-12-19 10:32:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:32:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:32:36 +2025-12-19 10:32:36 - 数据库插入成功 +2025-12-19 10:32:36 - 上传线程结束: /mnt/save/warning/alarm_20251219_103235.jpg: 成功! +2025-12-19 10:32:56 - 开始上传图片: /mnt/save/warning/alarm_20251219_103254.jpg +2025-12-19 10:32:56 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:32:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:32:56 +2025-12-19 10:32:57 - 数据库插入成功 +2025-12-19 10:32:57 - 上传线程结束: /mnt/save/warning/alarm_20251219_103254.jpg: 成功! +2025-12-19 10:33:00 - 开始上传图片: /mnt/save/warning/alarm_20251219_103258.jpg +2025-12-19 10:33:00 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:33:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:33:00 +2025-12-19 10:33:01 - 数据库插入成功 +2025-12-19 10:33:01 - 上传线程结束: /mnt/save/warning/alarm_20251219_103258.jpg: 成功! +2025-12-19 10:33:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:33:28 - 开始上传图片: /mnt/save/warning/alarm_20251219_103326.jpg +2025-12-19 10:33:28 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:33:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:33:28 +2025-12-19 10:33:29 - 数据库插入成功 +2025-12-19 10:33:29 - 上传线程结束: /mnt/save/warning/alarm_20251219_103326.jpg: 成功! +2025-12-19 10:33:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:33:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_103336.jpg +2025-12-19 10:33:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:33:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:33:38 +2025-12-19 10:33:39 - 数据库插入成功 +2025-12-19 10:33:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_103336.jpg: 成功! +2025-12-19 10:34:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:34:14 - 开始上传图片: /mnt/save/warning/alarm_20251219_103413.jpg +2025-12-19 10:34:14 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:34:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:34:14 +2025-12-19 10:34:15 - 数据库插入成功 +2025-12-19 10:34:15 - 上传线程结束: /mnt/save/warning/alarm_20251219_103413.jpg: 成功! +2025-12-19 10:34:28 - 开始上传图片: /mnt/save/warning/alarm_20251219_103427.jpg +2025-12-19 10:34:28 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:34:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:34:28 +2025-12-19 10:34:29 - 数据库插入成功 +2025-12-19 10:34:29 - 上传线程结束: /mnt/save/warning/alarm_20251219_103427.jpg: 成功! +2025-12-19 10:34:32 - 开始上传图片: /mnt/save/warning/alarm_20251219_103431.jpg +2025-12-19 10:34:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:34:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:34:32 +2025-12-19 10:34:33 - 数据库插入成功 +2025-12-19 10:34:33 - 上传线程结束: /mnt/save/warning/alarm_20251219_103431.jpg: 成功! +2025-12-19 10:34:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:34:36 - 开始上传图片: /mnt/save/warning/alarm_20251219_103435.jpg +2025-12-19 10:34:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:34:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:34:36 +2025-12-19 10:34:37 - 数据库插入成功 +2025-12-19 10:34:37 - 上传线程结束: /mnt/save/warning/alarm_20251219_103435.jpg: 成功! +2025-12-19 10:35:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:35:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_103511.jpg +2025-12-19 10:35:12 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:35:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:35:12 +2025-12-19 10:35:13 - 数据库插入成功 +2025-12-19 10:35:13 - 上传线程结束: /mnt/save/warning/alarm_20251219_103511.jpg: 成功! +2025-12-19 10:35:22 - 开始上传图片: /mnt/save/warning/alarm_20251219_103521.jpg +2025-12-19 10:35:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:35:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:35:22 +2025-12-19 10:35:23 - 数据库插入成功 +2025-12-19 10:35:23 - 上传线程结束: /mnt/save/warning/alarm_20251219_103521.jpg: 成功! +2025-12-19 10:35:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:35:42 - 开始上传图片: /mnt/save/warning/alarm_20251219_103541.jpg +2025-12-19 10:35:42 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:35:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:35:42 +2025-12-19 10:35:43 - 数据库插入成功 +2025-12-19 10:35:43 - 上传线程结束: /mnt/save/warning/alarm_20251219_103541.jpg: 成功! +2025-12-19 10:35:56 - 开始上传图片: /mnt/save/warning/alarm_20251219_103555.jpg +2025-12-19 10:35:56 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:35:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:35:56 +2025-12-19 10:35:57 - 数据库插入成功 +2025-12-19 10:35:57 - 上传线程结束: /mnt/save/warning/alarm_20251219_103555.jpg: 成功! +2025-12-19 10:36:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:36:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_103606.jpg +2025-12-19 10:36:08 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:36:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:36:08 +2025-12-19 10:36:09 - 数据库插入成功 +2025-12-19 10:36:09 - 上传线程结束: /mnt/save/warning/alarm_20251219_103606.jpg: 成功! +2025-12-19 10:36:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_103612.jpg +2025-12-19 10:36:12 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:36:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:36:12 +2025-12-19 10:36:13 - 数据库插入成功 +2025-12-19 10:36:13 - 上传线程结束: /mnt/save/warning/alarm_20251219_103612.jpg: 成功! +2025-12-19 10:36:16 - 开始上传图片: /mnt/save/warning/alarm_20251219_103615.jpg +2025-12-19 10:36:16 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:36:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:36:16 +2025-12-19 10:36:17 - 数据库插入成功 +2025-12-19 10:36:17 - 上传线程结束: /mnt/save/warning/alarm_20251219_103615.jpg: 成功! +2025-12-19 10:36:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:37:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:37:10 - 开始上传图片: /mnt/save/warning/alarm_20251219_103710.jpg +2025-12-19 10:37:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:37:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:37:10 +2025-12-19 10:37:12 - 数据库插入成功 +2025-12-19 10:37:12 - 上传线程结束: /mnt/save/warning/alarm_20251219_103710.jpg: 成功! +2025-12-19 10:37:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:38:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:38:33 - 开始上传图片: /mnt/save/warning/alarm_20251219_103832.jpg +2025-12-19 10:38:33 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:38:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:38:33 +2025-12-19 10:38:34 - 数据库插入成功 +2025-12-19 10:38:34 - 上传线程结束: /mnt/save/warning/alarm_20251219_103832.jpg: 成功! +2025-12-19 10:38:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:39:05 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:39:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_103910.jpg +2025-12-19 10:39:11 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:39:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:39:11 +2025-12-19 10:39:12 - 数据库插入成功 +2025-12-19 10:39:12 - 上传线程结束: /mnt/save/warning/alarm_20251219_103910.jpg: 成功! +2025-12-19 10:39:21 - 开始上传图片: /mnt/save/warning/alarm_20251219_103920.jpg +2025-12-19 10:39:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 10:39:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:39:21 +2025-12-19 10:39:22 - 数据库插入成功 +2025-12-19 10:39:22 - 上传线程结束: /mnt/save/warning/alarm_20251219_103920.jpg: 成功! +2025-12-19 10:39:35 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 10:39:41 - 开始上传图片: /mnt/2025-12-19 10:41:10 - ICCID刷新线程启动 +2025-12-19 10:41:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:41:27 - 开始上传图片: /mnt/save/warning/alarm_20251219_104125.jpg +2025-12-19 10:41:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:41:28 +2025-12-19 10:41:28 - 数据库插入成功 +2025-12-19 10:41:28 - 上传线程结束: /mnt/save/warning/alarm_20251219_104125.jpg: 成功! +2025-12-19 10:41:35 - 开始上传图片: /mnt/save/warning/alarm_20251219_104134.jpg +2025-12-19 10:41:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:41:36 +2025-12-19 10:41:37 - 数据库插入成功 +2025-12-19 10:41:37 - 上传线程结束: /mnt/save/warning/alarm_20251219_104134.jpg: 成功! +2025-12-19 10:41:49 - 开始上传图片: /mnt/save/warning/alarm_20251219_104148.jpg +2025-12-19 10:41:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:41:50 +2025-12-19 10:41:51 - 数据库插入成功 +2025-12-19 10:41:51 - 上传线程结束: /mnt/save/warning/alarm_20251219_104148.jpg: 成功! +2025-12-19 10:41:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:41:57 - 开始上传图片: /mnt/save/warning/alarm_20251219_104155.jpg +2025-12-19 10:41:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:41:58 +2025-12-19 10:41:58 - 数据库插入成功 +2025-12-19 10:41:58 - 上传线程结束: /mnt/save/warning/alarm_20251219_104155.jpg: 成功! +2025-12-19 10:42:13 - 开始上传图片: /mnt/save/warning/alarm_20251219_104211.jpg +2025-12-19 10:42:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:42:14 +2025-12-19 10:42:15 - 数据库插入成功 +2025-12-19 10:42:15 - 上传线程结束: /mnt/save/warning/alarm_20251219_104211.jpg: 成功! +2025-12-19 10:42:19 - 开始上传图片: /mnt/save/warning/alarm_20251219_104217.jpg +2025-12-19 10:42:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:42:20 +2025-12-19 10:42:21 - 数据库插入成功 +2025-12-19 10:42:21 - 上传线程结束: /mnt/save/warning/alarm_20251219_104217.jpg: 成功! +2025-12-19 10:42:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:43:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:43:45 - 开始上传图片: /mnt/save/warning/alarm_20251219_104344.jpg +2025-12-19 10:43:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:43:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:43:46 +2025-12-19 10:43:47 - 数据库插入成功 +2025-12-19 10:43:47 - 上传线程结束: /mnt/save/warning/alarm_20251219_104344.jpg: 成功! +2025-12-19 10:43:51 - 开始上传图片: /mnt/save/warning/alarm_20251219_104350.jpg +2025-12-19 10:43:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:43:52 +2025-12-19 10:43:53 - 数据库插入成功 +2025-12-19 10:43:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_104350.jpg: 成功! +2025-12-19 10:44:01 - 开始上传图片: /mnt/save/warning/alarm_20251219_104359.jpg +2025-12-19 10:44:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:44:02 +2025-12-19 10:44:03 - 数据库插入成功 +2025-12-19 10:44:03 - 上传线程结束: /mnt/save/warning/alarm_20251219_104359.jpg: 成功! +2025-12-19 10:44:05 - 开始上传图片: /mnt/save/warning/alarm_20251219_104403.jpg +2025-12-19 10:44:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:44:06 +2025-12-19 10:44:07 - 数据库插入成功 +2025-12-19 10:44:07 - 上传线程结束: /mnt/save/warning/alarm_20251219_104403.jpg: 成功! +2025-12-19 10:44:21 - 开始上传图片: /mnt/save/warning/alarm_20251219_104421.jpg +2025-12-19 10:44:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:44:22 +2025-12-19 10:44:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:44:23 - 数据库插入成功 +2025-12-19 10:44:23 - 上传线程结束: /mnt/save/warning/alarm_20251219_104421.jpg: 成功! +2025-12-19 10:45:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:45:03 - 开始上传图片: /mnt/save/warning/alarm_20251219_104502.jpg +2025-12-19 10:45:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:45:05 +2025-12-19 10:45:05 - 数据库插入成功 +2025-12-19 10:45:05 - 上传线程结束: /mnt/save/warning/alarm_20251219_104502.jpg: 成功! +2025-12-19 10:45:09 - 开始上传图片: /mnt/save/warning/alarm_20251219_104509.jpg +2025-12-19 10:45:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:45:11 +2025-12-19 10:45:11 - 数据库插入成功 +2025-12-19 10:45:11 - 上传线程结束: /mnt/save/warning/alarm_20251219_104509.jpg: 成功! +2025-12-19 10:45:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_104511.jpg +2025-12-19 10:45:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:45:13 +2025-12-19 10:45:14 - 数据库插入成功 +2025-12-19 10:45:14 - 上传线程结束: /mnt/save/warning/alarm_20251219_104511.jpg: 成功! +2025-12-19 10:45:17 - 开始上传图片: /mnt/save/warning/alarm_20251219_104517.jpg +2025-12-19 10:45:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:45:19 +2025-12-19 10:45:19 - 数据库插入成功 +2025-12-19 10:45:19 - 上传线程结束: /mnt/save/warning/alarm_20251219_104517.jpg: 成功! +2025-12-19 10:45:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:45:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_104537.jpg +2025-12-19 10:45:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:45:39 +2025-12-19 10:45:39 - 数据库插入成功 +2025-12-19 10:45:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_104537.jpg: 成功! +2025-12-19 10:45:56 - 开始上传图片: /mnt/save/warning/alarm_20251219_104554.jpg +2025-12-19 10:45:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:45:57 +2025-12-19 10:45:57 - 数据库插入成功 +2025-12-19 10:45:57 - 上传线程结束: /mnt/save/warning/alarm_20251219_104554.jpg: 成功! +2025-12-19 10:46:00 - 开始上传图片: /mnt/save/warning/alarm_20251219_104559.jpg +2025-12-19 10:46:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:46:01 +2025-12-19 10:46:01 - 数据库插入成功 +2025-12-19 10:46:01 - 上传线程结束: /mnt/save/warning/alarm_20251219_104559.jpg: 成功! +2025-12-19 10:46:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:46:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_104637.jpg +2025-12-19 10:46:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:46:39 +2025-12-19 10:46:39 - 数据库插入成功 +2025-12-19 10:46:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_104637.jpg: 成功! +2025-12-19 10:46:42 - 开始上传图片: /mnt/save/warning/alarm_20251219_104640.jpg +2025-12-19 10:46:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:46:43 +2025-12-19 10:46:43 - 数据库插入成功 +2025-12-19 10:46:43 - 上传线程结束: /mnt/save/warning/alarm_20251219_104640.jpg: 成功! +2025-12-19 10:46:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:46:56 - 开始上传图片: /mnt/save/warning/alarm_20251219_104655.jpg +2025-12-19 10:46:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:46:57 +2025-12-19 10:46:58 - 数据库插入成功 +2025-12-19 10:46:58 - 上传线程结束: /mnt/save/warning/alarm_20251219_104655.jpg: 成功! +2025-12-19 10:47:00 - 开始上传图片: /mnt/save/warning/alarm_20251219_104658.jpg +2025-12-19 10:47:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:47:01 +2025-12-19 10:47:02 - 数据库插入成功 +2025-12-19 10:47:02 - 上传线程结束: /mnt/save/warning/alarm_20251219_104658.jpg: 成功! +2025-12-19 10:47:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_104711.jpg +2025-12-19 10:47:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:47:13 +2025-12-19 10:47:13 - 数据库插入成功 +2025-12-19 10:47:13 - 上传线程结束: /mnt/save/warning/alarm_20251219_104711.jpg: 成功! +2025-12-19 10:47:14 - 开始上传图片: /mnt/save/warning/alarm_20251219_104714.jpg +2025-12-19 10:47:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:47:15 +2025-12-19 10:47:16 - 数据库插入成功 +2025-12-19 10:47:16 - 上传线程结束: /mnt/save/warning/alarm_20251219_104714.jpg: 成功! +2025-12-19 10:47:18 - 开始上传图片: /mnt/save/warning/alarm_20251219_104717.jpg +2025-12-19 10:47:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:47:19 +2025-12-19 10:47:20 - 数据库插入成功 +2025-12-19 10:47:20 - 上传线程结束: /mnt/save/warning/alarm_20251219_104717.jpg: 成功! +2025-12-19 10:47:24 - 开始上传图片: /mnt/save/warning/alarm_20251219_104724.jpg +2025-12-19 10:47:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:47:25 +2025-12-19 10:47:26 - 数据库插入成功 +2025-12-19 10:47:26 - 上传线程结束: /mnt/save/warning/alarm_20251219_104724.jpg: 成功! +2025-12-19 10:47:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:47:40 - 开始上传图片: /mnt/save/warning/alarm_20251219_104739.jpg +2025-12-19 10:47:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:47:41 +2025-12-19 10:47:42 - 数据库插入成功 +2025-12-19 10:47:42 - 上传线程结束: /mnt/save/warning/alarm_20251219_104739.jpg: 成功! +2025-12-19 10:47:48 - 开始上传图片: /mnt/save/warning/alarm_20251219_104747.jpg +2025-12-19 10:47:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:47:49 +2025-12-19 10:47:50 - 数据库插入成功 +2025-12-19 10:47:50 - 上传线程结束: /mnt/save/warning/alarm_20251219_104747.jpg: 成功! +2025-12-19 10:47:54 - 开始上传图片: /mnt/save/warning/alarm_20251219_104754.jpg +2025-12-19 10:47:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:47:55 +2025-12-19 10:47:56 - 数据库插入成功 +2025-12-19 10:47:56 - 上传线程结束: /mnt/save/warning/alarm_20251219_104754.jpg: 成功! +2025-12-19 10:47:58 - 开始上传图片: /mnt/save/warning/alarm_20251219_104756.jpg +2025-12-19 10:47:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:47:59 +2025-12-19 10:48:00 - 数据库插入成功 +2025-12-19 10:48:00 - 上传线程结束: /mnt/save/warning/alarm_20251219_104756.jpg: 成功! +2025-12-19 10:48:02 - 开始上传图片: /mnt/save/warning/alarm_20251219_104800.jpg +2025-12-19 10:48:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:48:03 +2025-12-19 10:48:04 - 数据库插入成功 +2025-12-19 10:48:04 - 上传线程结束: /mnt/save/warning/alarm_20251219_104800.jpg: 成功! +2025-12-19 10:48:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:48:26 - 开始上传图片: /mnt/save/warning/alarm_20251219_104825.jpg +2025-12-19 10:48:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:48:27 +2025-12-19 10:48:28 - 数据库插入成功 +2025-12-19 10:48:28 - 上传线程结束: /mnt/save/warning/alarm_20251219_104825.jpg: 成功! +2025-12-19 10:48:34 - 开始上传图片: /mnt/save/warning/alarm_20251219_104834.jpg +2025-12-19 10:48:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:48:35 +2025-12-19 10:48:36 - 数据库插入成功 +2025-12-19 10:48:36 - 上传线程结束: /mnt/save/warning/alarm_20251219_104834.jpg: 成功! +2025-12-19 10:48:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:49:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:49:53 - 开始上传图片: /mnt/save/warning/alarm_20251219_104951.jpg +2025-12-19 10:49:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:49:54 +2025-12-19 10:49:54 - 数据库插入成功 +2025-12-19 10:49:55 - 上传线程结束: /mnt/save/warning/alarm_20251219_104951.jpg: 成功! +2025-12-19 10:49:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:50:09 - 开始上传图片: /mnt/save/warning/alarm_20251219_105008.jpg +2025-12-19 10:50:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:50:10 +2025-12-19 10:50:11 - 数据库插入成功 +2025-12-19 10:50:11 - 上传线程结束: /mnt/save/warning/alarm_20251219_105008.jpg: 成功! +2025-12-19 10:50:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:50:57 - 开始上传图片: /mnt/save/warning/alarm_20251219_105056.jpg +2025-12-19 10:50:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:50:58 +2025-12-19 10:50:59 - 数据库插入成功 +2025-12-19 10:50:59 - 上传线程结束: /mnt/save/warning/alarm_20251219_105056.jpg: 成功! +2025-12-19 10:51:03 - 开始上传图片: /mnt/save/warning/alarm_20251219_105101.jpg +2025-12-19 10:51:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:51:04 +2025-12-19 10:51:05 - 数据库插入成功 +2025-12-19 10:51:05 - 上传线程结束: /mnt/save/warning/alarm_20251219_105101.jpg: 成功! +2025-12-19 10:51:10 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:51:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:52:24 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:53:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:53:09 - 开始上传图片: /mnt/save/warning/alarm_20251219_105309.jpg +2025-12-19 10:53:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:53:10 +2025-12-19 10:53:11 - 数据库插入成功 +2025-12-19 10:53:11 - 上传线程结束: /mnt/save/warning/alarm_20251219_105309.jpg: 成功! +2025-12-19 10:53:13 - 开始上传图片: /mnt/save/warning/alarm_20251219_105312.jpg +2025-12-19 10:53:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:53:14 +2025-12-19 10:53:15 - 开始上传图片: /mnt/save/warning/alarm_20251219_105315.jpg +2025-12-19 10:53:15 - 数据库插入成功 +2025-12-19 10:53:15 - 上传线程结束: /mnt/save/warning/alarm_20251219_105312.jpg: 成功! +2025-12-19 10:53:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:53:16 +2025-12-19 10:53:17 - 数据库插入成功 +2025-12-19 10:53:17 - 上传线程结束: /mnt/save/warning/alarm_20251219_105315.jpg: 成功! +2025-12-19 10:53:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:54:05 - 开始上传图片: /mnt/save/warning/alarm_20251219_105405.jpg +2025-12-19 10:54:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:54:06 +2025-12-19 10:54:07 - 数据库插入成功 +2025-12-19 10:54:07 - 上传线程结束: /mnt/save/warning/alarm_20251219_105405.jpg: 成功! +2025-12-19 10:54:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:54:39 - 开始上传图片: /mnt/save/warning/alarm_20251219_105439.jpg +2025-12-19 10:54:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:54:41 +2025-12-19 10:54:41 - 数据库插入成功 +2025-12-19 10:54:41 - 上传线程结束: /mnt/save/warning/alarm_20251219_105439.jpg: 成功! +2025-12-19 10:54:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:55:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_105507.jpg +2025-12-19 10:55:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:55:09 +2025-12-19 10:55:10 - 数据库插入成功 +2025-12-19 10:55:10 - 上传线程结束: /mnt/save/warning/alarm_20251219_105507.jpg: 成功! +2025-12-19 10:55:28 - 开始上传图片: /mnt/save/warning/alarm_20251219_105527.jpg +2025-12-19 10:55:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:55:29 +2025-12-19 10:55:29 - 数据库插入成功 +2025-12-19 10:55:29 - 上传线程结束: /mnt/save/warning/alarm_20251219_105527.jpg: 成功! +2025-12-19 10:55:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:55:34 - 开始上传图片: /mnt/save/warning/alarm_20251219_105532.jpg +2025-12-19 10:55:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:55:35 +2025-12-19 10:55:36 - 数据库插入成功 +2025-12-19 10:55:36 - 上传线程结束: /mnt/save/warning/alarm_20251219_105532.jpg: 成功! +2025-12-19 10:56:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:56:10 - 开始上传图片: /mnt/save/warning/alarm_20251219_105609.jpg +2025-12-19 10:56:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:56:11 +2025-12-19 10:56:12 - 数据库插入成功 +2025-12-19 10:56:12 - 上传线程结束: /mnt/save/warning/alarm_20251219_105609.jpg: 成功! +2025-12-19 10:56:20 - 开始上传图片: /mnt/save/warning/alarm_20251219_105618.jpg +2025-12-19 10:56:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:56:21 +2025-12-19 10:56:22 - 数据库插入成功 +2025-12-19 10:56:22 - 上传线程结束: /mnt/save/warning/alarm_20251219_105618.jpg: 成功! +2025-12-19 10:56:24 - 开始上传图片: /mnt/save/warning/alarm_20251219_105623.jpg +2025-12-19 10:56:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:56:25 +2025-12-19 10:56:26 - 数据库插入成功 +2025-12-19 10:56:26 - 上传线程结束: /mnt/save/warning/alarm_20251219_105623.jpg: 成功! +2025-12-19 10:56:30 - 开始上传图片: /mnt/save/warning/alarm_20251219_105629.jpg +2025-12-19 10:56:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:56:31 +2025-12-19 10:56:32 - 数据库插入成功 +2025-12-19 10:56:32 - 上传线程结束: /mnt/save/warning/alarm_20251219_105629.jpg: 成功! +2025-12-19 10:56:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:56:48 - 开始上传图片: /mnt/save/warning/alarm_20251219_105648.jpg +2025-12-19 10:56:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:56:49 +2025-12-19 10:56:51 - 数据库插入成功 +2025-12-19 10:56:51 - 上传线程结束: /mnt/save/warning/alarm_20251219_105648.jpg: 成功! +2025-12-19 10:57:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:57:28 - 开始上传图片: /mnt/save/warning/alarm_20251219_105728.jpg +2025-12-19 10:57:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:57:29 +2025-12-19 10:57:30 - 数据库插入成功 +2025-12-19 10:57:30 - 上传线程结束: /mnt/save/warning/alarm_20251219_105728.jpg: 成功! +2025-12-19 10:57:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_105737.jpg +2025-12-19 10:57:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:57:39 +2025-12-19 10:57:40 - 数据库插入成功 +2025-12-19 10:57:40 - 上传线程结束: /mnt/save/warning/alarm_20251219_105737.jpg: 成功! +2025-12-19 10:57:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:58:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_105811.jpg +2025-12-19 10:58:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:58:13 +2025-12-19 10:58:14 - 数据库插入成功 +2025-12-19 10:58:14 - 上传线程结束: /mnt/save/warning/alarm_20251219_105811.jpg: 成功! +2025-12-19 10:58:16 - 开始上传图片: /mnt/save/warning/alarm_20251219_105815.jpg +2025-12-19 10:58:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:58:17 +2025-12-19 10:58:18 - 数据库插入成功 +2025-12-19 10:58:18 - 上传线程结束: /mnt/save/warning/alarm_20251219_105815.jpg: 成功! +2025-12-19 10:58:32 - 开始上传图片: /mnt/save/warning/alarm_20251219_105831.jpg +2025-12-19 10:58:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:58:33 +2025-12-19 10:58:34 - 数据库插入成功 +2025-12-19 10:58:34 - 上传线程结束: /mnt/save/warning/alarm_20251219_105831.jpg: 成功! +2025-12-19 10:58:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:58:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_105837.jpg +2025-12-19 10:58:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:58:39 +2025-12-19 10:58:40 - 数据库插入成功 +2025-12-19 10:58:40 - 上传线程结束: /mnt/save/warning/alarm_20251219_105837.jpg: 成功! +2025-12-19 10:58:44 - 开始上传图片: /mnt/save/warning/alarm_20251219_105844.jpg +2025-12-19 10:58:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:58:45 +2025-12-19 10:58:46 - 数据库插入成功 +2025-12-19 10:58:46 - 上传线程结束: /mnt/save/warning/alarm_20251219_105844.jpg: 成功! +2025-12-19 10:58:50 - 开始上传图片: /mnt/save/warning/alarm_20251219_105849.jpg +2025-12-19 10:58:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:58:52 +2025-12-19 10:58:52 - 数据库插入成功 +2025-12-19 10:58:52 - 上传线程结束: /mnt/save/warning/alarm_20251219_105849.jpg: 成功! +2025-12-19 10:58:54 - 开始上传图片: /mnt/save/warning/alarm_20251219_105852.jpg +2025-12-19 10:58:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:58:56 +2025-12-19 10:58:56 - 数据库插入成功 +2025-12-19 10:58:56 - 上传线程结束: /mnt/save/warning/alarm_20251219_105852.jpg: 成功! +2025-12-19 10:58:58 - 开始上传图片: /mnt/save/warning/alarm_20251219_105858.jpg +2025-12-19 10:59:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:59:00 +2025-12-19 10:59:00 - 数据库插入成功 +2025-12-19 10:59:00 - 上传线程结束: /mnt/save/warning/alarm_20251219_105858.jpg: 成功! +2025-12-19 10:59:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 10:59:25 - 开始上传图片: /mnt/save/warning/alarm_20251219_105923.jpg +2025-12-19 10:59:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 10:59:26 +2025-12-19 10:59:27 - 数据库插入成功 +2025-12-19 10:59:27 - 上传线程结束: /mnt/save/warning/alarm_20251219_105923.jpg: 成功! +2025-12-19 10:59:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:00:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:01:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:01:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:02:17 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:02:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:03:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:04:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:04:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_110410.jpg +2025-12-19 11:04:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:04:13 +2025-12-19 11:04:14 - 数据库插入成功 +2025-12-19 11:04:14 - 上传线程结束: /mnt/save/warning/alarm_20251219_110410.jpg: 成功! +2025-12-19 11:04:42 - 开始上传图片: /mnt/save/warning/alarm_20251219_110440.jpg +2025-12-19 11:04:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:04:43 +2025-12-19 11:04:44 - 数据库插入成功 +2025-12-19 11:04:44 - 上传线程结束: /mnt/save/warning/alarm_20251219_110440.jpg: 成功! +2025-12-19 11:04:45 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:05:16 - 开始上传图片: /mnt/save/warning/alarm_20251219_110515.jpg +2025-12-19 11:05:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:05:17 +2025-12-19 11:05:18 - 数据库插入成功 +2025-12-19 11:05:18 - 上传线程结束: /mnt/save/warning/alarm_20251219_110515.jpg: 成功! +2025-12-19 11:05:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:05:56 - 开始上传图片: /mnt/save/warning/alarm_20251219_110555.jpg +2025-12-19 11:05:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:05:57 +2025-12-19 11:05:58 - 数据库插入成功 +2025-12-19 11:05:58 - 上传线程结束: /mnt/save/warning/alarm_20251219_110555.jpg: 成功! +2025-12-19 11:05:59 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:06:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:07:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:07:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:08:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:09:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:09:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:10:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:10:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:11:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:12:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:12:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:13:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:14:01 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:14:38 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:15:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:15:52 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:16:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:17:06 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:17:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:18:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:18:57 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:19:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:20:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:20:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:21:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:22:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:22:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:23:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:23:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:24:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:25:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:25:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:26:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:26:58 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:27:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:28:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:28:45 - 开始上传图片: /mnt/save/warning/alarm_20251219_112844.jpg +2025-12-19 11:28:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:28:46 +2025-12-19 11:28:47 - 数据库插入成功 +2025-12-19 11:28:47 - 上传线程结束: /mnt/save/warning/alarm_20251219_112844.jpg: 成功! +2025-12-19 11:28:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:29:13 - 开始上传图片: /mnt/save/warning/alarm_20251219_112912.jpg +2025-12-19 11:29:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:29:14 +2025-12-19 11:29:14 - 数据库插入成功 +2025-12-19 11:29:14 - 上传线程结束: /mnt/save/warning/alarm_20251219_112912.jpg: 成功! +2025-12-19 11:29:17 - 开始上传图片: /mnt/save/warning/alarm_20251219_112916.jpg +2025-12-19 11:29:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:29:18 +2025-12-19 11:29:19 - 数据库插入成功 +2025-12-19 11:29:19 - 上传线程结束: /mnt/save/warning/alarm_20251219_112916.jpg: 成功! +2025-12-19 11:29:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:30:04 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:30:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:31:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:31:23 - 开始上传图片: /mnt/save/warning/alarm_20251219_113122.jpg +2025-12-19 11:31:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:31:25 +2025-12-19 11:31:26 - 数据库插入成功 +2025-12-19 11:31:26 - 上传线程结束: /mnt/save/warning/alarm_20251219_113122.jpg: 成功! +2025-12-19 11:31:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:32:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:32:42 - 开始上传图片: /mnt/save/warning/alarm_20251219_113240.jpg +2025-12-19 11:32:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:32:43 +2025-12-19 11:32:44 - 数据库插入成功 +2025-12-19 11:32:44 - 上传线程结束: /mnt/save/warning/alarm_20251219_113240.jpg: 成功! +2025-12-19 11:32:46 - 开始上传图片: /mnt/save/warning/alarm_20251219_113245.jpg +2025-12-19 11:32:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:32:47 +2025-12-19 11:32:48 - 数据库插入成功 +2025-12-19 11:32:48 - 上传线程结束: /mnt/save/warning/alarm_20251219_113245.jpg: 成功! +2025-12-19 11:32:58 - 开始上传图片: /mnt/save/warning/alarm_20251219_113256.jpg +2025-12-19 11:32:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:32:59 +2025-12-19 11:33:00 - 数据库插入成功 +2025-12-19 11:33:00 - 上传线程结束: /mnt/save/warning/alarm_20251219_113256.jpg: 成功! +2025-12-19 11:33:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:33:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:34:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:35:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:35:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:17:08 - ICCID刷新线程启动 +2025-12-19 11:17:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_112916.jpg +2025-12-19 11:17:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_113256.jpg +2025-12-19 11:17:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_112844.jpg +2025-12-19 11:17:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_112912.jpg +2025-12-19 11:17:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_113122.jpg +2025-12-19 11:17:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_113240.jpg +2025-12-19 11:17:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_113245.jpg +2025-12-19 11:17:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_113927.jpg +2025-12-19 11:17:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:17:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:17:15 +2025-12-19 11:17:16 - 数据库插入成功 +2025-12-19 11:17:16 - 上传线程结束: /mnt/save/warning/alarm_20251219_113927.jpg: 成功! +2025-12-19 11:17:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:17:16 +2025-12-19 11:17:17 - 数据库插入成功 +2025-12-19 11:17:17 - 上传线程结束: /mnt/save/warning/alarm_20251219_113245.jpg: 成功! +2025-12-19 11:41:13 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:41:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:41:13 +2025-12-19 11:41:14 - 数据库插入成功 +2025-12-19 11:41:14 - 上传线程结束: /mnt/save/warning/alarm_20251219_112844.jpg: 成功! +2025-12-19 11:41:19 - 开始上传图片: /mnt/save/warning/alarm_20251219_114118.jpg +2025-12-19 11:41:20 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:41:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:41:20 +2025-12-19 11:41:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:41:20 +2025-12-19 11:41:21 - 数据库插入成功 +2025-12-19 11:41:21 - 上传线程结束: /mnt/save/warning/alarm_20251219_113240.jpg: 成功! +2025-12-19 11:41:21 - 数据库插入成功 +2025-12-19 11:41:21 - 上传线程结束: /mnt/save/warning/alarm_20251219_114118.jpg: 成功! +2025-12-19 11:41:27 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:41:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:41:27 +2025-12-19 11:41:28 - 数据库插入成功 +2025-12-19 11:41:28 - 上传线程结束: /mnt/save/warning/alarm_20251219_112912.jpg: 成功! +2025-12-19 11:41:29 - 开始上传图片: /mnt/save/warning/alarm_20251219_114127.jpg +2025-12-19 11:41:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:41:30 +2025-12-19 11:41:31 - 数据库插入成功 +2025-12-19 11:41:31 - 上传线程结束: /mnt/save/warning/alarm_20251219_114127.jpg: 成功! +2025-12-19 11:41:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:41:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:41:34 +2025-12-19 11:41:35 - 数据库插入成功 +2025-12-19 11:41:35 - 上传线程结束: /mnt/save/warning/alarm_20251219_112916.jpg: 成功! +2025-12-19 11:41:41 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:41:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:41:41 +2025-12-19 11:41:42 - 数据库插入成功 +2025-12-19 11:41:42 - 上传线程结束: /mnt/save/warning/alarm_20251219_113256.jpg: 成功! +2025-12-19 11:41:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:41:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:41:48 +2025-12-19 11:41:49 - 数据库插入成功 +2025-12-19 11:41:49 - 上传线程结束: /mnt/save/warning/alarm_20251219_113122.jpg: 成功! +2025-12-19 11:41:55 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:41:59 - 开始上传图片: /mnt/save/warning/alarm_20251219_114158.jpg +2025-12-19 11:42:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:42:00 +2025-12-19 11:42:01 - 数据库插入成功 +2025-12-19 11:42:01 - 上传线程结束: /mnt/save/warning/alarm_20251219_114158.jpg: 成功! +2025-12-19 11:42:07 - 开始上传图片: /mnt/save/warning/alarm_20251219_114207.jpg +2025-12-19 11:42:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:42:08 +2025-12-19 11:42:09 - 数据库插入成功 +2025-12-19 11:42:09 - 上传线程结束: /mnt/save/warning/alarm_20251219_114207.jpg: 成功! +2025-12-19 11:42:21 - 开始上传图片: /mnt/save/warning/alarm_20251219_114221.jpg +2025-12-19 11:42:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:42:22 +2025-12-19 11:42:23 - 数据库插入成功 +2025-12-19 11:42:23 - 上传线程结束: /mnt/save/warning/alarm_20251219_114221.jpg: 成功! +2025-12-19 11:42:32 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:42:35 - 开始上传图片: /mnt/save/warning/alarm_20251219_114233.jpg +2025-12-19 11:42:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:42:36 +2025-12-19 11:42:37 - 数据库插入成功 +2025-12-19 11:42:37 - 上传线程结束: /mnt/save/warning/alarm_20251219_114233.jpg: 成功! +2025-12-19 11:42:43 - 开始上传图片: /mnt/save/warning/alarm_20251219_114242.jpg +2025-12-19 11:42:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:42:44 +2025-12-19 11:42:45 - 数据库插入成功 +2025-12-19 11:42:45 - 上传线程结束: /mnt/save/warning/alarm_20251219_114242.jpg: 成功! +2025-12-19 11:42:47 - 开始上传图片: /mnt/save/warning/alarm_20251219_114247.jpg +2025-12-19 11:42:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:42:49 +2025-12-19 11:42:49 - 数据库插入成功 +2025-12-19 11:42:49 - 上传线程结束: /mnt/save/warning/alarm_20251219_114247.jpg: 成功! +2025-12-19 11:42:53 - 开始上传图片: /mnt/save/warning/alarm_20251219_114252.jpg +2025-12-19 11:42:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:42:55 +2025-12-19 11:42:55 - 数据库插入成功 +2025-12-19 11:42:55 - 上传线程结束: /mnt/save/warning/alarm_20251219_114252.jpg: 成功! +2025-12-19 11:42:57 - 开始上传图片: /mnt/save/warning/alarm_20251219_114257.jpg +2025-12-19 11:42:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:42:59 +2025-12-19 11:42:59 - 数据库插入成功 +2025-12-19 11:42:59 - 上传线程结束: /mnt/save/warning/alarm_20251219_114257.jpg: 成功! +2025-12-19 11:43:02 - 开始上传图片: /mnt/save/warning/alarm_20251219_114300.jpg +2025-12-19 11:43:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:43:03 +2025-12-19 11:43:03 - 数据库插入成功 +2025-12-19 11:43:03 - 上传线程结束: /mnt/save/warning/alarm_20251219_114300.jpg: 成功! +2025-12-19 11:43:09 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:43:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_114337.jpg +2025-12-19 11:43:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:43:39 +2025-12-19 11:43:39 - 数据库插入成功 +2025-12-19 11:43:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_114337.jpg: 成功! +2025-12-19 11:43:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:44:23 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:44:44 - 开始上传图片: /mnt/save/warning/alarm_20251219_114442.jpg +2025-12-19 11:44:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:44:45 +2025-12-19 11:44:46 - 数据库插入成功 +2025-12-19 11:44:46 - 上传线程结束: /mnt/save/warning/alarm_20251219_114442.jpg: 成功! +2025-12-19 11:44:52 - 开始上传图片: /mnt/save/warning/alarm_20251219_114450.jpg +2025-12-19 11:44:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:44:53 +2025-12-19 11:44:54 - 数据库插入成功 +2025-12-19 11:44:54 - 上传线程结束: /mnt/save/warning/alarm_20251219_114450.jpg: 成功! +2025-12-19 11:45:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:45:37 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:46:02 - 开始上传图片: /mnt/save/warning/alarm_20251219_114602.jpg +2025-12-19 11:46:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:46:03 +2025-12-19 11:46:04 - 数据库插入成功 +2025-12-19 11:46:04 - 上传线程结束: /mnt/save/warning/alarm_20251219_114602.jpg: 成功! +2025-12-19 11:46:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:46:34 - 开始上传图片: /mnt/save/warning/alarm_20251219_114633.jpg +2025-12-19 11:46:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:46:35 +2025-12-19 11:46:36 - 数据库插入成功 +2025-12-19 11:46:36 - 上传线程结束: /mnt/save/warning/alarm_20251219_114633.jpg: 成功! +2025-12-19 11:46:48 - 开始上传图片: /mnt/save/warning/alarm_20251219_114647.jpg +2025-12-19 11:46:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:46:50 +2025-12-19 11:46:50 - 数据库插入成功 +2025-12-19 11:46:50 - 上传线程结束: /mnt/save/warning/alarm_20251219_114647.jpg: 成功! +2025-12-19 11:46:51 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:47:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_114712.jpg +2025-12-19 11:47:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:47:14 +2025-12-19 11:47:14 - 数据库插入成功 +2025-12-19 11:47:14 - 上传线程结束: /mnt/save/warning/alarm_20251219_114712.jpg: 成功! +2025-12-19 11:47:16 - 开始上传图片: /mnt/save/warning/alarm_20251219_114715.jpg +2025-12-19 11:47:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:47:18 +2025-12-19 11:47:18 - 数据库插入成功 +2025-12-19 11:47:18 - 上传线程结束: /mnt/save/warning/alarm_20251219_114715.jpg: 成功! +2025-12-19 11:47:20 - 开始上传图片: /mnt/save/warning/alarm_20251219_114719.jpg +2025-12-19 11:47:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:47:22 +2025-12-19 11:47:22 - 数据库插入成功 +2025-12-19 11:47:22 - 上传线程结束: /mnt/save/warning/alarm_20251219_114719.jpg: 成功! +2025-12-19 11:47:25 - 开始上传图片: /mnt/save/warning/alarm_20251219_114723.jpg +2025-12-19 11:47:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:47:26 +2025-12-19 11:47:26 - 数据库插入成功 +2025-12-19 11:47:26 - 上传线程结束: /mnt/save/warning/alarm_20251219_114723.jpg: 成功! +2025-12-19 11:47:27 - 开始上传图片: /mnt/save/warning/alarm_20251219_114726.jpg +2025-12-19 11:47:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:47:28 +2025-12-19 11:47:28 - 数据库插入成功 +2025-12-19 11:47:28 - 上传线程结束: /mnt/save/warning/alarm_20251219_114726.jpg: 成功! +2025-12-19 11:47:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:47:41 - 开始上传图片: /mnt/save/warning/alarm_20251219_114739.jpg +2025-12-19 11:47:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:47:42 +2025-12-19 11:47:42 - 数据库插入成功 +2025-12-19 11:47:42 - 上传线程结束: /mnt/save/warning/alarm_20251219_114739.jpg: 成功! +2025-12-19 11:47:53 - 开始上传图片: /mnt/save/warning/alarm_20251219_114751.jpg +2025-12-19 11:47:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:47:54 +2025-12-19 11:47:54 - 数据库插入成功 +2025-12-19 11:47:54 - 上传线程结束: /mnt/save/warning/alarm_20251219_114751.jpg: 成功! +2025-12-19 11:48:05 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:48:15 - 开始上传图片: /mnt/save/warning/alarm_20251219_114814.jpg +2025-12-19 11:48:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:48:16 +2025-12-19 11:48:17 - 数据库插入成功 +2025-12-19 11:48:17 - 上传线程结束: /mnt/save/warning/alarm_20251219_114814.jpg: 成功! +2025-12-19 11:48:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:48:47 - 开始上传图片: /mnt/save/warning/alarm_20251219_114846.jpg +2025-12-19 11:48:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:48:48 +2025-12-19 11:48:49 - 数据库插入成功 +2025-12-19 11:48:49 - 上传线程结束: /mnt/save/warning/alarm_20251219_114846.jpg: 成功! +2025-12-19 11:49:05 - 开始上传图片: /mnt/save/warning/alarm_20251219_114904.jpg +2025-12-19 11:49:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:49:06 +2025-12-19 11:49:07 - 数据库插入成功 +2025-12-19 11:49:07 - 上传线程结束: /mnt/save/warning/alarm_20251219_114904.jpg: 成功! +2025-12-19 11:49:09 - 开始上传图片: /mnt/save/warning/alarm_20251219_114908.jpg +2025-12-19 11:49:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:49:10 +2025-12-19 11:49:11 - 数据库插入成功 +2025-12-19 11:49:11 - 上传线程结束: /mnt/save/warning/alarm_20251219_114908.jpg: 成功! +2025-12-19 11:49:15 - 开始上传图片: /mnt/save/warning/alarm_20251219_114913.jpg +2025-12-19 11:49:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:49:16 +2025-12-19 11:49:17 - 数据库插入成功 +2025-12-19 11:49:17 - 上传线程结束: /mnt/save/warning/alarm_20251219_114913.jpg: 成功! +2025-12-19 11:49:17 - 开始上传图片: /mnt/save/warning/alarm_20251219_114916.jpg +2025-12-19 11:49:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:49:18 +2025-12-19 11:49:19 - 数据库插入成功 +2025-12-19 11:49:19 - 上传线程结束: /mnt/save/warning/alarm_20251219_114916.jpg: 成功! +2025-12-19 11:49:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:49:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:50:34 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:51:11 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:51:48 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:52:25 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:53:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:53:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:54:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:54:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:55:30 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:56:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:56:44 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:57:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:57:51 - 开始上传图片: /mnt/save/warning/alarm_20251219_115750.jpg +2025-12-19 11:17:11 - ICCID刷新线程启动 +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_112916.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113256.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114118.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114158.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114207.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114233.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114242.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114252.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114257.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114450.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114715.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114719.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114739.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114751.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114908.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114913.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114916.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_115750.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_112844.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_112912.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113122.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113240.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113245.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113927.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114127.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114221.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114247.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114300.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114337.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114442.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114602.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114633.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114647.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114712.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114723.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114726.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114814.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114846.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114904.jpg +2025-12-19 11:17:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:17:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:17:18 +2025-12-19 11:17:19 - 数据库插入成功 +2025-12-19 11:17:19 - 上传线程结束: /mnt/save/warning/alarm_20251219_114252.jpg: 成功! +2025-12-19 11:17:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:17:19 +2025-12-19 12:04:16 - 数据库插入成功 +2025-12-19 12:04:16 - 上传线程结束: /mnt/save/warning/alarm_20251219_112916.jpg: 成功! +2025-12-19 12:04:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:17 +2025-12-19 12:04:17 - 数据库插入成功 +2025-12-19 12:04:17 - 上传线程结束: /mnt/save/warning/alarm_20251219_115750.jpg: 成功! +2025-12-19 12:04:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:18 +2025-12-19 12:04:18 - 数据库插入成功 +2025-12-19 12:04:18 - 上传线程结束: /mnt/save/warning/alarm_20251219_113256.jpg: 成功! +2025-12-19 12:04:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:19 +2025-12-19 12:04:19 - 数据库插入成功 +2025-12-19 12:04:19 - 上传线程结束: /mnt/save/warning/alarm_20251219_114242.jpg: 成功! +2025-12-19 12:04:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:20 +2025-12-19 12:04:20 - 数据库插入成功 +2025-12-19 12:04:20 - 上传线程结束: /mnt/save/warning/alarm_20251219_114450.jpg: 成功! +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 读取ICCID异常: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:04:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=None, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=None, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=None, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=None, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=None, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=None, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:21 +2025-12-19 12:04:23 - 数据库插入成功 +2025-12-19 12:04:23 - 上传线程结束: /mnt/save/warning/alarm_20251219_114257.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114442.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114726.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114712.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114300.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_112912.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114647.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114715.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114913.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_113245.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_112844.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114158.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114846.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114221.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114127.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114723.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114739.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114207.jpg: 成功! +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114233.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114719.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114247.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114118.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114337.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114633.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114916.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114814.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114908.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_113927.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_113240.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_113122.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114904.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114751.jpg: 成功! +2025-12-19 12:04:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_114602.jpg: 成功! +2025-12-19 12:04:26 - 开始上传图片: /mnt/save/warning/alarm_20251219_120425.jpg +2025-12-19 12:04:26 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:26 +2025-12-19 12:04:27 - 数据库插入成功 +2025-12-19 12:04:27 - 上传线程结束: /mnt/save/warning/alarm_20251219_120425.jpg: 成功! +2025-12-19 12:04:34 - 开始上传图片: /mnt/save/warning/alarm_20251219_120433.jpg +2025-12-19 12:04:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:34 +2025-12-19 12:04:35 - 数据库插入成功 +2025-12-19 12:04:35 - 上传线程结束: /mnt/save/warning/alarm_20251219_120433.jpg: 成功! +2025-12-19 12:04:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:04:50 - 开始上传图片: /mnt/save/warning/alarm_20251219_120449.jpg +2025-12-19 12:04:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:04:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:04:50 +2025-12-19 12:04:51 - 数据库插入成功 +2025-12-19 12:04:51 - 上传线程结束: /mnt/save/warning/alarm_20251219_120449.jpg: 成功! +2025-12-19 12:05:00 - 开始上传图片: /mnt/save/warning/alarm_20251219_120459.jpg +2025-12-19 12:05:00 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:05:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:05:00 +2025-12-19 12:05:02 - 数据库插入成功 +2025-12-19 12:05:02 - 上传线程结束: /mnt/save/warning/alarm_20251219_120459.jpg: 成功! +2025-12-19 12:05:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_120507.jpg +2025-12-19 12:05:11 - 上传图片 /mnt/save/warning/alarm_20251219_120507.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:05:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120511.jpg +2025-12-19 12:05:14 - 上传图片 /mnt/save/warning/alarm_20251219_120511.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:05:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:05:22 - 开始上传图片: /mnt/save/warning/alarm_20251219_120520.jpg +2025-12-19 12:05:24 - 上传图片 /mnt/save/warning/alarm_20251219_120520.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:05:34 - 开始上传图片: /mnt/save/warning/alarm_20251219_120533.jpg +2025-12-19 12:05:37 - 上传图片 /mnt/save/warning/alarm_20251219_120533.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:05:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_120536.jpg +2025-12-19 12:05:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:05:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:05:38 +2025-12-19 12:05:39 - 数据库插入成功 +2025-12-19 12:05:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_120536.jpg: 成功! +2025-12-19 12:05:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:06:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:06:44 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:07:06 - 开始上传图片: /mnt/save/warning/alarm_20251219_120704.jpg +2025-12-19 12:07:06 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:07:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:07:06 +2025-12-19 12:07:07 - 数据库插入成功 +2025-12-19 12:07:07 - 上传线程结束: /mnt/save/warning/alarm_20251219_120704.jpg: 成功! +2025-12-19 12:07:14 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:07:38 - 开始上传图片: /mnt/save/warning/alarm_20251219_120738.jpg +2025-12-19 12:07:41 - 上传图片 /mnt/save/warning/alarm_20251219_120738.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:07:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:07:55 - 开始上传图片: /mnt/save/warning/alarm_20251219_120753.jpg +2025-12-19 12:07:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:07:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:07:55 +2025-12-19 12:08:00 - 数据库插入成功 +2025-12-19 12:08:00 - 上传线程结束: /mnt/save/warning/alarm_20251219_120753.jpg: 成功! +2025-12-19 12:08:01 - 开始上传图片: /mnt/save/warning/alarm_20251219_120800.jpg +2025-12-19 12:08:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:08:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:08:01 +2025-12-19 12:08:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:08:17 - 开始上传图片: /mnt/save/warning/alarm_20251219_120817.jpg +2025-12-19 12:08:19 - 上传图片 /mnt/save/warning/alarm_20251219_120817.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:08:27 - 开始上传图片: /mnt/save/warning/alarm_20251219_120825.jpg +2025-12-19 12:08:30 - 上传图片 /mnt/save/warning/alarm_20251219_120825.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:08:41 - 开始上传图片: /mnt/save/warning/alarm_20251219_120839.jpg +2025-12-19 12:08:44 - 上传图片 /mnt/save/warning/alarm_20251219_120839.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:08:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:08:45 - 开始上传图片: /mnt/save/warning/alarm_20251219_120843.jpg +2025-12-19 12:08:47 - 上传图片 /mnt/save/warning/alarm_20251219_120843.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:08:55 - 开始上传图片: /mnt/save/warning/alarm_20251219_120853.jpg +2025-12-19 12:08:57 - 上传图片 /mnt/save/warning/alarm_20251219_120853.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:08:59 - 开始上传图片: /mnt/save/warning/alarm_20251219_120857.jpg +2025-12-19 12:09:00 - 上传图片 /mnt/save/warning/alarm_20251219_120857.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:09:03 - 开始上传图片: /mnt/save/warning/alarm_20251219_120901.jpg +2025-12-19 12:09:04 - 上传图片 /mnt/save/warning/alarm_20251219_120901.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:09:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:09:41 - 开始上传图片: /mnt/save/warning/alarm_20251219_120941.jpg +2025-12-19 12:09:43 - 上传图片 /mnt/save/warning/alarm_20251219_120941.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:09:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:10:01 - 开始上传图片: /mnt/save/warning/alarm_20251219_120959.jpg +2025-12-19 12:10:04 - 上传图片 /mnt/save/warning/alarm_20251219_120959.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:10:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:10:23 - 开始上传图片: /mnt/save/warning/alarm_20251219_121022.jpg +2025-12-19 12:10:24 - 上传图片 /mnt/save/warning/alarm_20251219_121022.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:10:33 - 开始上传图片: /mnt/save/warning/alarm_20251219_121032.jpg +2025-12-19 12:10:34 - 上传图片 /mnt/save/warning/alarm_20251219_121032.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:10:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:10:49 - 开始上传图片: /mnt/save/warning/alarm_20251219_121049.jpg +2025-12-19 12:10:52 - 上传图片 /mnt/save/warning/alarm_20251219_121049.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:11:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:11:20 - 开始上传图片: /mnt/save/warning/alarm_20251219_121118.jpg +2025-12-19 12:11:20 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:11:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:11:20 +2025-12-19 12:11:21 - 数据库插入成功 +2025-12-19 12:11:21 - 上传线程结束: /mnt/save/warning/alarm_20251219_121118.jpg: 成功! +2025-12-19 12:11:36 - 开始上传图片: /mnt/save/warning/alarm_20251219_121134.jpg +2025-12-19 12:11:39 - 上传图片 /mnt/save/warning/alarm_20251219_121134.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:11:44 - 开始上传图片: /mnt/save/warning/alarm_20251219_121142.jpg +2025-12-19 12:11:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:11:47 - 上传图片 /mnt/save/warning/alarm_20251219_121142.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:11:48 - 开始上传图片: /mnt/save/warning/alarm_20251219_121146.jpg +2025-12-19 12:11:48 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:11:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:11:48 +2025-12-19 12:11:48 - 数据库插入成功 +2025-12-19 12:11:48 - 上传线程结束: /mnt/save/warning/alarm_20251219_120800.jpg: 成功! +2025-12-19 12:11:50 - 数据库插入成功 +2025-12-19 12:11:50 - 上传线程结束: /mnt/save/warning/alarm_20251219_121146.jpg: 成功! +2025-12-19 12:11:56 - 开始上传图片: /mnt/save/warning/alarm_20251219_121155.jpg +2025-12-19 12:11:56 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:11:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:11:56 +2025-12-19 12:11:57 - 数据库插入成功 +2025-12-19 12:11:57 - 上传线程结束: /mnt/save/warning/alarm_20251219_121155.jpg: 成功! +2025-12-19 12:12:00 - 开始上传图片: /mnt/save/warning/alarm_20251219_121159.jpg +2025-12-19 12:12:00 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:12:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:12:00 +2025-12-19 12:12:01 - 数据库插入成功 +2025-12-19 12:12:01 - 上传线程结束: /mnt/save/warning/alarm_20251219_121159.jpg: 成功! +2025-12-19 12:12:04 - 开始上传图片: /mnt/save/warning/alarm_20251219_121203.jpg +2025-12-19 12:12:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:12:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:12:04 +2025-12-19 12:12:05 - 数据库插入成功 +2025-12-19 12:12:05 - 上传线程结束: /mnt/save/warning/alarm_20251219_121203.jpg: 成功! +2025-12-19 12:12:08 - 开始上传图片: /mnt/save/warning/alarm_20251219_121206.jpg +2025-12-19 12:12:08 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:12:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:12:08 +2025-12-19 12:12:09 - 数据库插入成功 +2025-12-19 12:12:09 - 上传线程结束: /mnt/save/warning/alarm_20251219_121206.jpg: 成功! +2025-12-19 12:12:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:12:18 - 开始上传图片: /mnt/save/warning/alarm_20251219_121216.jpg +2025-12-19 12:12:22 - 开始上传图片: /mnt/save/warning/alarm_20251219_121222.jpg +2025-12-19 12:12:23 - 上传图片 /mnt/save/warning/alarm_20251219_121216.jpg 时出错: timed out +2025-12-19 12:12:26 - 上传图片 /mnt/save/warning/alarm_20251219_121222.jpg 时出错: [Errno 113] No route to host +2025-12-19 12:12:32 - 开始上传图片: /mnt/save/warning/alarm_20251219_121231.jpg +2025-12-19 12:12:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:12:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:12:32 +2025-12-19 12:12:33 - 数据库插入成功 +2025-12-19 12:12:33 - 上传线程结束: /mnt/save/warning/alarm_20251219_121231.jpg: 成功! +2025-12-19 12:12:42 - 开始上传图片: /mnt/save/warning/alarm_20251219_121240.jpg +2025-12-19 12:12:42 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:12:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:12:42 +2025-12-19 12:12:43 - 数据库插入成功 +2025-12-19 12:12:43 - 上传线程结束: /mnt/save/warning/alarm_20251219_121240.jpg: 成功! +2025-12-19 12:12:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:13:00 - 开始上传图片: /mnt/save/warning/alarm_20251219_121258.jpg +2025-12-19 12:13:00 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:13:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:13:00 +2025-12-19 12:13:01 - 数据库插入成功 +2025-12-19 12:13:01 - 上传线程结束: /mnt/save/warning/alarm_20251219_121258.jpg: 成功! +2025-12-19 12:13:02 - 开始上传图片: /mnt/save/warning/alarm_20251219_121302.jpg +2025-12-19 12:13:02 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:13:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:13:02 +2025-12-19 12:13:03 - 数据库插入成功 +2025-12-19 12:13:03 - 上传线程结束: /mnt/save/warning/alarm_20251219_121302.jpg: 成功! +2025-12-19 12:13:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:13:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:14:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:14:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:15:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:15:45 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 12:15:55 - 开始上传图片: /mnt/save/warning/alarm_20251219_121555.jpg +2025-12-19 12:15:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:15:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:15:55 +2025-12-19 12:15:56 - 数据库插入成功 +2025-12-19 12:15:56 - 上传线程结束: /mnt/save/warning/alarm_20251219_121555.jpg: 成功! +2025-12-19 12:16:03 - 开始上传图片: /mnt/save/warning/alarm_20251219_121601.jpg +2025-12-19 12:16:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-19 12:16:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 12:16:03 +2025-12-19 12:16:04 - 数据库插入成功 +2025-12-19 12:16:04 - 上传线程结束: /mnt/save/warning/alarm_20251219_121601.jpg: 成功! +2025-12-19 12:16:15 - 读取ICCID异常: (5, 'Input/output error') +2025-12-19 11:17:11 - ICCID刷新线程启动 +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_112916.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113256.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114118.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114158.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114207.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114233.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114242.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114252.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114257.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114450.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114715.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114719.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114739.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114751.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114908.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114913.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114916.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_115750.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_112844.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_112912.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113122.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113240.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113245.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_113927.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114127.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114221.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114247.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114300.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114337.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114442.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114602.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114633.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114647.jpg +2025-12-19 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251219_114712.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_114723.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_114726.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_114814.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_114846.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_114904.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120425.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120449.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120459.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120507.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120511.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120533.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120536.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120704.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120738.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120753.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120825.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120839.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120853.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120901.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120941.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120959.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121032.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120520.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120800.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120843.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120857.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121022.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121155.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121203.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121216.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121222.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121049.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121118.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121142.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121159.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121206.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121258.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121555.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120433.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_120817.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121134.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121146.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121302.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121231.jpg +2025-12-19 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251219_121240.jpg +2025-12-19 11:17:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-19 11:17:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:17:18 +2025-12-19 11:17:19 - 数据库插入成功 +2025-12-19 11:17:19 - 上传线程结束: /mnt/save/warning/alarm_20251219_114257.jpg: 成功! +2025-12-19 11:17:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-19 11:17:19 +2025-12-20 11:13:10 - 数据库插入成功 +2025-12-20 11:13:10 - 上传线程结束: /mnt/save/warning/alarm_20251219_114233.jpg: 成功! +2025-12-20 11:13:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:10 +2025-12-20 11:13:11 - 数据库插入成功 +2025-12-20 11:13:11 - 上传线程结束: /mnt/save/warning/alarm_20251219_114242.jpg: 成功! +2025-12-20 11:13:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:11 +2025-12-20 11:13:11 - 数据库插入成功 +2025-12-20 11:13:11 - 上传线程结束: /mnt/save/warning/alarm_20251219_112916.jpg: 成功! +2025-12-20 11:13:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:12 +2025-12-20 11:13:12 - 数据库插入成功 +2025-12-20 11:13:12 - 上传线程结束: /mnt/save/warning/alarm_20251219_114751.jpg: 成功! +2025-12-20 11:13:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:13 +2025-12-20 11:13:13 - 数据库插入成功 +2025-12-20 11:13:13 - 上传线程结束: /mnt/save/warning/alarm_20251219_114450.jpg: 成功! +2025-12-20 11:13:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:14 +2025-12-20 11:13:15 - 读取ICCID成功: 898604581824D0366321 +2025-12-20 11:13:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:15 +2025-12-20 11:13:15 - 数据库插入成功 +2025-12-20 11:13:15 - 上传线程结束: /mnt/save/warning/alarm_20251219_114252.jpg: 成功! +2025-12-20 11:13:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:15 +2025-12-20 11:13:16 - 数据库插入成功 +2025-12-20 11:13:16 - 上传线程结束: /mnt/save/warning/alarm_20251219_114118.jpg: 成功! +2025-12-20 11:13:16 - 数据库插入成功 +2025-12-20 11:13:16 - 上传线程结束: /mnt/save/warning/alarm_20251219_114916.jpg: 成功! +2025-12-20 11:13:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:16 +2025-12-20 11:13:16 - 数据库插入成功 +2025-12-20 11:13:16 - 上传线程结束: /mnt/save/warning/alarm_20251219_115750.jpg: 成功! +2025-12-20 11:13:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:17 +2025-12-20 11:13:18 - 数据库插入成功 +2025-12-20 11:13:18 - 上传线程结束: /mnt/save/warning/alarm_20251219_114739.jpg: 成功! +2025-12-20 11:13:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:18 +2025-12-20 11:13:18 - 数据库插入成功 +2025-12-20 11:13:18 - 上传线程结束: /mnt/save/warning/alarm_20251219_114913.jpg: 成功! +2025-12-20 11:13:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:19 +2025-12-20 11:13:20 - 数据库插入成功 +2025-12-20 11:13:20 - 上传线程结束: /mnt/save/warning/alarm_20251219_114908.jpg: 成功! +2025-12-20 11:13:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:20 +2025-12-20 11:13:21 - 数据库插入成功 +2025-12-20 11:13:21 - 上传线程结束: /mnt/save/warning/alarm_20251219_112912.jpg: 成功! +2025-12-20 11:13:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:21 +2025-12-20 11:13:21 - 数据库插入成功 +2025-12-20 11:13:21 - 上传线程结束: /mnt/save/warning/alarm_20251219_113927.jpg: 成功! +2025-12-20 11:13:22 - 读取ICCID成功: 898604581824D0366321 +2025-12-20 11:13:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:22 +2025-12-20 11:13:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:22 +2025-12-20 11:13:22 - 数据库插入成功 +2025-12-20 11:13:22 - 上传线程结束: /mnt/save/warning/alarm_20251219_114207.jpg: 成功! +2025-12-20 11:13:23 - 数据库插入成功 +2025-12-20 11:13:23 - 上传线程结束: /mnt/save/warning/alarm_20251219_112844.jpg: 成功! +2025-12-20 11:13:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:23 +2025-12-20 11:13:24 - 数据库插入成功 +2025-12-20 11:13:24 - 上传线程结束: /mnt/save/warning/alarm_20251219_113240.jpg: 成功! +2025-12-20 11:13:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:24 +2025-12-20 11:13:25 - 数据库插入成功 +2025-12-20 11:13:25 - 上传线程结束: /mnt/save/warning/alarm_20251219_113245.jpg: 成功! +2025-12-20 11:13:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:25 +2025-12-20 11:13:26 - 数据库插入成功 +2025-12-20 11:13:26 - 上传线程结束: /mnt/save/warning/alarm_20251219_113122.jpg: 成功! +2025-12-20 11:13:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:26 +2025-12-20 11:13:26 - 数据库插入成功 +2025-12-20 11:13:26 - 上传线程结束: /mnt/save/warning/alarm_20251219_114221.jpg: 成功! +2025-12-20 11:13:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:27 +2025-12-20 11:13:28 - 数据库插入成功 +2025-12-20 11:13:28 - 上传线程结束: /mnt/save/warning/alarm_20251219_114127.jpg: 成功! +2025-12-20 11:13:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:28 +2025-12-20 11:13:28 - 数据库插入成功 +2025-12-20 11:13:28 - 上传线程结束: /mnt/save/warning/alarm_20251219_114442.jpg: 成功! +2025-12-20 11:13:29 - 读取ICCID成功: 898604581824D0366321 +2025-12-20 11:13:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:29 +2025-12-20 11:13:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:29 +2025-12-20 11:13:30 - 数据库插入成功 +2025-12-20 11:13:30 - 上传线程结束: /mnt/save/warning/alarm_20251219_113256.jpg: 成功! +2025-12-20 11:13:30 - 数据库插入成功 +2025-12-20 11:13:30 - 上传线程结束: /mnt/save/warning/alarm_20251219_114247.jpg: 成功! +2025-12-20 11:13:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:30 +2025-12-20 11:13:31 - 数据库插入成功 +2025-12-20 11:13:31 - 上传线程结束: /mnt/save/warning/alarm_20251219_114647.jpg: 成功! +2025-12-20 11:13:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:31 +2025-12-20 11:13:31 - 数据库插入成功 +2025-12-20 11:13:31 - 上传线程结束: /mnt/save/warning/alarm_20251219_114337.jpg: 成功! +2025-12-20 11:13:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:32 +2025-12-20 11:13:33 - 数据库插入成功 +2025-12-20 11:13:33 - 上传线程结束: /mnt/save/warning/alarm_20251219_114712.jpg: 成功! +2025-12-20 11:13:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:33 +2025-12-20 11:13:33 - 数据库插入成功 +2025-12-20 11:13:33 - 上传线程结束: /mnt/save/warning/alarm_20251219_114300.jpg: 成功! +2025-12-20 11:13:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:34 +2025-12-20 11:13:35 - 数据库插入成功 +2025-12-20 11:13:35 - 上传线程结束: /mnt/save/warning/alarm_20251219_114633.jpg: 成功! +2025-12-20 11:13:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:35 +2025-12-20 11:13:36 - 读取ICCID成功: 898604581824D0366321 +2025-12-20 11:13:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:36 +2025-12-20 11:13:36 - 数据库插入成功 +2025-12-20 11:13:36 - 上传线程结束: /mnt/save/warning/alarm_20251219_114602.jpg: 成功! +2025-12-20 11:13:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:36 +2025-12-20 11:13:37 - 数据库插入成功 +2025-12-20 11:13:37 - 上传线程结束: /mnt/save/warning/alarm_20251219_114158.jpg: 成功! +2025-12-20 11:13:37 - 数据库插入成功 +2025-12-20 11:13:37 - 上传线程结束: /mnt/save/warning/alarm_20251219_114726.jpg: 成功! +2025-12-20 11:13:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:37 +2025-12-20 11:13:38 - 数据库插入成功 +2025-12-20 11:13:38 - 上传线程结束: /mnt/save/warning/alarm_20251219_114723.jpg: 成功! +2025-12-20 11:13:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:38 +2025-12-20 11:13:39 - 数据库插入成功 +2025-12-20 11:13:39 - 上传线程结束: /mnt/save/warning/alarm_20251219_114846.jpg: 成功! +2025-12-20 11:13:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:39 +2025-12-20 11:13:40 - 数据库插入成功 +2025-12-20 11:13:40 - 上传线程结束: /mnt/save/warning/alarm_20251219_120459.jpg: 成功! +2025-12-20 11:13:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:40 +2025-12-20 11:13:41 - 数据库插入成功 +2025-12-20 11:13:41 - 上传线程结束: /mnt/save/warning/alarm_20251219_120507.jpg: 成功! +2025-12-20 11:13:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:41 +2025-12-20 11:13:42 - 数据库插入成功 +2025-12-20 11:13:42 - 上传线程结束: /mnt/save/warning/alarm_20251219_120449.jpg: 成功! +2025-12-20 11:13:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:42 +2025-12-20 11:13:43 - 数据库插入成功 +2025-12-20 11:13:43 - 上传线程结束: /mnt/save/warning/alarm_20251219_114814.jpg: 成功! +2025-12-20 11:13:43 - 读取ICCID成功: 898604581824D0366321 +2025-12-20 11:13:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:43 +2025-12-20 11:13:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:43 +2025-12-20 11:13:43 - 数据库插入成功 +2025-12-20 11:13:43 - 上传线程结束: /mnt/save/warning/alarm_20251219_114719.jpg: 成功! +2025-12-20 11:13:44 - 数据库插入成功 +2025-12-20 11:13:44 - 上传线程结束: /mnt/save/warning/alarm_20251219_114904.jpg: 成功! +2025-12-20 11:13:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:44 +2025-12-20 11:13:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:45 +2025-12-20 11:13:45 - 数据库插入成功 +2025-12-20 11:13:45 - 上传线程结束: /mnt/save/warning/alarm_20251219_120425.jpg: 成功! +2025-12-20 11:13:46 - 数据库插入成功 +2025-12-20 11:13:46 - 上传线程结束: /mnt/save/warning/alarm_20251219_120536.jpg: 成功! +2025-12-20 11:13:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:46 +2025-12-20 11:13:47 - 数据库插入成功 +2025-12-20 11:13:47 - 上传线程结束: /mnt/save/warning/alarm_20251219_120753.jpg: 成功! +2025-12-20 11:13:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:47 +2025-12-20 11:13:48 - 数据库插入成功 +2025-12-20 11:13:48 - 上传线程结束: /mnt/save/warning/alarm_20251219_120511.jpg: 成功! +2025-12-20 11:13:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:48 +2025-12-20 11:13:49 - 数据库插入成功 +2025-12-20 11:13:49 - 上传线程结束: /mnt/save/warning/alarm_20251219_120839.jpg: 成功! +2025-12-20 11:13:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:49 +2025-12-20 11:13:50 - 数据库插入成功 +2025-12-20 11:13:50 - 上传线程结束: /mnt/save/warning/alarm_20251219_120959.jpg: 成功! +2025-12-20 11:13:50 - 读取ICCID成功: 898604581824D0366321 +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 读取ICCID异常: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:50 +2025-12-20 11:13:52 - 数据库插入成功 +2025-12-20 11:13:52 - 上传线程结束: /mnt/save/warning/alarm_20251219_114715.jpg: 成功! +2025-12-20 11:13:52 - 数据库插入成功 +2025-12-20 11:13:52 - 上传线程结束: /mnt/save/warning/alarm_20251219_120704.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120533.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121155.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121203.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121118.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121258.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121240.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120825.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121142.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121146.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121222.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120843.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121231.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120433.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121206.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121022.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121302.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121134.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121216.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120520.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120941.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120853.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121032.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121159.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121049.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120738.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120901.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120817.jpg: 成功! +2025-12-20 11:13:53 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' ([Errno 113] No route to host)") +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_121555.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120857.jpg: 成功! +2025-12-20 11:13:53 - 上传线程结束: /mnt/save/warning/alarm_20251219_120800.jpg: 成功! +2025-12-20 11:13:53 - 开始上传图片: /mnt/save/warning/alarm_20251220_111353.jpg +2025-12-20 11:13:53 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:13:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:13:53 +2025-12-20 11:13:54 - 数据库插入成功 +2025-12-20 11:13:54 - 上传线程结束: /mnt/save/warning/alarm_20251220_111353.jpg: 成功! +2025-12-20 11:14:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:14:32 - 开始上传图片: /mnt/save/warning/alarm_20251220_111431.jpg +2025-12-20 11:14:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:14:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:14:32 +2025-12-20 11:14:33 - 数据库插入成功 +2025-12-20 11:14:33 - 上传线程结束: /mnt/save/warning/alarm_20251220_111431.jpg: 成功! +2025-12-20 11:14:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:15:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:15:24 - 开始上传图片: /mnt/save/warning/alarm_20251220_111524.jpg +2025-12-20 11:15:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:15:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:15:24 +2025-12-20 11:15:25 - 数据库插入成功 +2025-12-20 11:15:25 - 上传线程结束: /mnt/save/warning/alarm_20251220_111524.jpg: 成功! +2025-12-20 11:15:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:15:50 - 开始上传图片: /mnt/save/warning/alarm_20251220_111550.jpg +2025-12-20 11:15:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:15:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:15:50 +2025-12-20 11:15:51 - 数据库插入成功 +2025-12-20 11:15:51 - 上传线程结束: /mnt/save/warning/alarm_20251220_111550.jpg: 成功! +2025-12-20 11:16:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:16:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:17:00 - 开始上传图片: /mnt/save/warning/alarm_20251220_111659.jpg +2025-12-20 11:17:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:17:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:01 +2025-12-20 11:17:01 - 数据库插入成功 +2025-12-20 11:17:01 - 上传线程结束: /mnt/save/warning/alarm_20251220_111659.jpg: 成功! +2025-12-20 11:17:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:17:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:18:01 - 开始上传图片: /mnt/save/warning/alarm_20251220_111800.jpg +2025-12-20 11:18:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:18:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:18:01 +2025-12-20 11:18:02 - 数据库插入成功 +2025-12-20 11:18:02 - 上传线程结束: /mnt/save/warning/alarm_20251220_111800.jpg: 成功! +2025-12-20 11:18:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:18:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:19:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:19:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:20:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:20:40 - 开始上传图片: /mnt/save/warning/alarm_20251220_112039.jpg +2025-12-20 11:20:40 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:20:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:20:40 +2025-12-20 11:20:41 - 数据库插入成功 +2025-12-20 11:20:41 - 上传线程结束: /mnt/save/warning/alarm_20251220_112039.jpg: 成功! +2025-12-20 11:20:46 - 开始上传图片: /mnt/save/warning/alarm_20251220_112045.jpg +2025-12-20 11:20:46 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:20:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:20:46 +2025-12-20 11:20:47 - 数据库插入成功 +2025-12-20 11:20:47 - 上传线程结束: /mnt/save/warning/alarm_20251220_112045.jpg: 成功! +2025-12-20 11:20:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:21:20 - 开始上传图片: /mnt/save/warning/alarm_20251220_112118.jpg +2025-12-20 11:21:20 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:21:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:21:20 +2025-12-20 11:21:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:21:21 - 数据库插入成功 +2025-12-20 11:21:21 - 上传线程结束: /mnt/save/warning/alarm_20251220_112118.jpg: 成功! +2025-12-20 11:21:22 - 开始上传图片: /mnt/save/warning/alarm_20251220_112121.jpg +2025-12-20 11:21:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:21:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:21:22 +2025-12-20 11:21:23 - 数据库插入成功 +2025-12-20 11:21:23 - 上传线程结束: /mnt/save/warning/alarm_20251220_112121.jpg: 成功! +2025-12-20 11:21:34 - 开始上传图片: /mnt/save/warning/alarm_20251220_112134.jpg +2025-12-20 11:21:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:21:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:21:34 +2025-12-20 11:21:35 - 数据库插入成功 +2025-12-20 11:21:35 - 上传线程结束: /mnt/save/warning/alarm_20251220_112134.jpg: 成功! +2025-12-20 11:21:44 - 开始上传图片: /mnt/save/warning/alarm_20251220_112144.jpg +2025-12-20 11:21:44 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:21:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:21:44 +2025-12-20 11:21:45 - 数据库插入成功 +2025-12-20 11:21:45 - 上传线程结束: /mnt/save/warning/alarm_20251220_112144.jpg: 成功! +2025-12-20 11:21:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:22:02 - 开始上传图片: /mnt/save/warning/alarm_20251220_112201.jpg +2025-12-20 11:22:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:22:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:22:03 +2025-12-20 11:22:03 - 数据库插入成功 +2025-12-20 11:22:03 - 上传线程结束: /mnt/save/warning/alarm_20251220_112201.jpg: 成功! +2025-12-20 11:22:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:22:47 - 开始上传图片: /mnt/save/warning/alarm_20251220_112245.jpg +2025-12-20 11:22:47 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:22:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:22:47 +2025-12-20 11:22:47 - 数据库插入成功 +2025-12-20 11:22:47 - 上传线程结束: /mnt/save/warning/alarm_20251220_112245.jpg: 成功! +2025-12-20 11:22:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:22:53 - 开始上传图片: /mnt/save/warning/alarm_20251220_112252.jpg +2025-12-20 11:22:53 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:22:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:22:53 +2025-12-20 11:22:54 - 数据库插入成功 +2025-12-20 11:22:54 - 上传线程结束: /mnt/save/warning/alarm_20251220_112252.jpg: 成功! +2025-12-20 11:22:57 - 开始上传图片: /mnt/save/warning/alarm_20251220_112257.jpg +2025-12-20 11:22:57 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:22:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:22:57 +2025-12-20 11:22:58 - 数据库插入成功 +2025-12-20 11:22:58 - 上传线程结束: /mnt/save/warning/alarm_20251220_112257.jpg: 成功! +2025-12-20 11:23:05 - 开始上传图片: /mnt/save/warning/alarm_20251220_112304.jpg +2025-12-20 11:23:05 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:23:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:23:05 +2025-12-20 11:23:05 - 数据库插入成功 +2025-12-20 11:23:05 - 上传线程结束: /mnt/save/warning/alarm_20251220_112304.jpg: 成功! +2025-12-20 11:23:07 - 开始上传图片: /mnt/save/warning/alarm_20251220_112306.jpg +2025-12-20 11:23:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:23:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:23:07 +2025-12-20 11:23:08 - 数据库插入成功 +2025-12-20 11:23:08 - 上传线程结束: /mnt/save/warning/alarm_20251220_112306.jpg: 成功! +2025-12-20 11:23:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112310.jpg +2025-12-20 11:23:11 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:23:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:23:11 +2025-12-20 11:23:12 - 数据库插入成功 +2025-12-20 11:23:12 - 上传线程结束: /mnt/save/warning/alarm_20251220_112310.jpg: 成功! +2025-12-20 11:23:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:23:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:24:19 - 开始上传图片: /mnt/save/warning/alarm_20251220_112418.jpg +2025-12-20 11:24:19 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:24:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:24:19 +2025-12-20 11:24:20 - 数据库插入成功 +2025-12-20 11:24:20 - 上传线程结束: /mnt/save/warning/alarm_20251220_112418.jpg: 成功! +2025-12-20 11:24:20 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:24:47 - 开始上传图片: /mnt/save/warning/alarm_20251220_112447.jpg +2025-12-20 11:24:47 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:24:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:24:47 +2025-12-20 11:24:48 - 数据库插入成功 +2025-12-20 11:24:48 - 上传线程结束: /mnt/save/warning/alarm_20251220_112447.jpg: 成功! +2025-12-20 11:24:50 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:25:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:25:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:26:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:26:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:27:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:27:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:27:52 - 开始上传图片: /mnt/save/warning/alarm_20251220_112752.jpg +2025-12-20 11:27:52 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:27:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:27:52 +2025-12-20 11:27:53 - 数据库插入成功 +2025-12-20 11:27:53 - 上传线程结束: /mnt/save/warning/alarm_20251220_112752.jpg: 成功! +2025-12-20 11:28:02 - 开始上传图片: /mnt/save/warning/alarm_20251220_112801.jpg +2025-12-20 11:28:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:28:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:28:03 +2025-12-20 11:28:03 - 数据库插入成功 +2025-12-20 11:28:03 - 上传线程结束: /mnt/save/warning/alarm_20251220_112801.jpg: 成功! +2025-12-20 11:28:08 - 开始上传图片: /mnt/save/warning/alarm_20251220_112808.jpg +2025-12-20 11:28:09 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:28:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:28:09 +2025-12-20 11:28:09 - 数据库插入成功 +2025-12-20 11:28:09 - 上传线程结束: /mnt/save/warning/alarm_20251220_112808.jpg: 成功! +2025-12-20 11:28:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:28:23 - 开始上传图片: /mnt/save/warning/alarm_20251220_112821.jpg +2025-12-20 11:28:23 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:28:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:28:23 +2025-12-20 11:28:23 - 数据库插入成功 +2025-12-20 11:28:23 - 上传线程结束: /mnt/save/warning/alarm_20251220_112821.jpg: 成功! +2025-12-20 11:28:37 - 开始上传图片: /mnt/save/warning/alarm_20251220_112835.jpg +2025-12-20 11:28:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:28:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:28:37 +2025-12-20 11:28:37 - 数据库插入成功 +2025-12-20 11:28:37 - 上传线程结束: /mnt/save/warning/alarm_20251220_112835.jpg: 成功! +2025-12-20 11:28:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:28:51 - 开始上传图片: /mnt/save/warning/alarm_20251220_112849.jpg +2025-12-20 11:28:51 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:28:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:28:51 +2025-12-20 11:28:51 - 数据库插入成功 +2025-12-20 11:28:51 - 上传线程结束: /mnt/save/warning/alarm_20251220_112849.jpg: 成功! +2025-12-20 11:29:01 - 开始上传图片: /mnt/save/warning/alarm_20251220_112900.jpg +2025-12-20 11:29:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:29:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:29:01 +2025-12-20 11:29:02 - 数据库插入成功 +2025-12-20 11:29:02 - 上传线程结束: /mnt/save/warning/alarm_20251220_112900.jpg: 成功! +2025-12-20 11:29:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:29:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:29:59 - 开始上传图片: /mnt/save/warning/alarm_20251220_112958.jpg +2025-12-20 11:29:59 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:29:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:29:59 +2025-12-20 11:30:00 - 数据库插入成功 +2025-12-20 11:30:00 - 上传线程结束: /mnt/save/warning/alarm_20251220_112958.jpg: 成功! +2025-12-20 11:30:07 - 开始上传图片: /mnt/save/warning/alarm_20251220_113005.jpg +2025-12-20 11:30:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:30:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:30:07 +2025-12-20 11:30:08 - 数据库插入成功 +2025-12-20 11:30:08 - 上传线程结束: /mnt/save/warning/alarm_20251220_113005.jpg: 成功! +2025-12-20 11:30:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113008.jpg +2025-12-20 11:30:09 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:30:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:30:09 +2025-12-20 11:30:10 - 数据库插入成功 +2025-12-20 11:30:10 - 上传线程结束: /mnt/save/warning/alarm_20251220_113008.jpg: 成功! +2025-12-20 11:30:17 - 开始上传图片: /mnt/save/warning/alarm_20251220_113017.jpg +2025-12-20 11:30:17 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:30:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:30:17 +2025-12-20 11:30:18 - 数据库插入成功 +2025-12-20 11:30:18 - 上传线程结束: /mnt/save/warning/alarm_20251220_113017.jpg: 成功! +2025-12-20 11:30:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:30:25 - 开始上传图片: /mnt/save/warning/alarm_20251220_113024.jpg +2025-12-20 11:30:25 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:30:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:30:25 +2025-12-20 11:30:26 - 数据库插入成功 +2025-12-20 11:30:26 - 上传线程结束: /mnt/save/warning/alarm_20251220_113024.jpg: 成功! +2025-12-20 11:30:31 - 开始上传图片: /mnt/save/warning/alarm_20251220_113030.jpg +2025-12-20 11:30:31 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:30:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:30:31 +2025-12-20 11:30:32 - 数据库插入成功 +2025-12-20 11:30:32 - 上传线程结束: /mnt/save/warning/alarm_20251220_113030.jpg: 成功! +2025-12-20 11:30:35 - 开始上传图片: /mnt/save/warning/alarm_20251220_113035.jpg +2025-12-20 11:30:35 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:30:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:30:35 +2025-12-20 11:30:36 - 数据库插入成功 +2025-12-20 11:30:36 - 上传线程结束: /mnt/save/warning/alarm_20251220_113035.jpg: 成功! +2025-12-20 11:30:43 - 开始上传图片: /mnt/save/warning/alarm_20251220_113043.jpg +2025-12-20 11:30:43 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:30:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:30:43 +2025-12-20 11:30:44 - 数据库插入成功 +2025-12-20 11:30:44 - 上传线程结束: /mnt/save/warning/alarm_20251220_113043.jpg: 成功! +2025-12-20 11:30:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:31:17 - 开始上传图片: /mnt/save/warning/alarm_20251220_113116.jpg +2025-12-20 11:31:17 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:31:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:31:17 +2025-12-20 11:31:18 - 数据库插入成功 +2025-12-20 11:31:18 - 上传线程结束: /mnt/save/warning/alarm_20251220_113116.jpg: 成功! +2025-12-20 11:31:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:31:29 - 开始上传图片: /mnt/save/warning/alarm_20251220_113128.jpg +2025-12-20 11:31:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:31:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:31:29 +2025-12-20 11:31:30 - 数据库插入成功 +2025-12-20 11:31:30 - 上传线程结束: /mnt/save/warning/alarm_20251220_113128.jpg: 成功! +2025-12-20 11:31:35 - 开始上传图片: /mnt/save/warning/alarm_20251220_113134.jpg +2025-12-20 11:31:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:31:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:31:36 +2025-12-20 11:31:36 - 数据库插入成功 +2025-12-20 11:31:36 - 上传线程结束: /mnt/save/warning/alarm_20251220_113134.jpg: 成功! +2025-12-20 11:31:39 - 开始上传图片: /mnt/save/warning/alarm_20251220_113138.jpg +2025-12-20 11:31:40 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:31:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:31:40 +2025-12-20 11:31:40 - 数据库插入成功 +2025-12-20 11:31:40 - 上传线程结束: /mnt/save/warning/alarm_20251220_113138.jpg: 成功! +2025-12-20 11:31:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:31:56 - 开始上传图片: /mnt/save/warning/alarm_20251220_113155.jpg +2025-12-20 11:31:56 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:31:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:31:56 +2025-12-20 11:31:57 - 数据库插入成功 +2025-12-20 11:31:57 - 上传线程结束: /mnt/save/warning/alarm_20251220_113155.jpg: 成功! +2025-12-20 11:31:58 - 开始上传图片: /mnt/save/warning/alarm_20251220_113157.jpg +2025-12-20 11:31:58 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:31:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:31:58 +2025-12-20 11:31:59 - 数据库插入成功 +2025-12-20 11:31:59 - 上传线程结束: /mnt/save/warning/alarm_20251220_113157.jpg: 成功! +2025-12-20 11:32:04 - 开始上传图片: /mnt/save/warning/alarm_20251220_113202.jpg +2025-12-20 11:32:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:04 +2025-12-20 11:32:04 - 数据库插入成功 +2025-12-20 11:32:04 - 上传线程结束: /mnt/save/warning/alarm_20251220_113202.jpg: 成功! +2025-12-20 11:32:08 - 开始上传图片: /mnt/save/warning/alarm_20251220_113207.jpg +2025-12-20 11:32:08 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:08 +2025-12-20 11:32:09 - 数据库插入成功 +2025-12-20 11:32:09 - 上传线程结束: /mnt/save/warning/alarm_20251220_113207.jpg: 成功! +2025-12-20 11:32:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113211.jpg +2025-12-20 11:32:12 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:12 +2025-12-20 11:32:13 - 数据库插入成功 +2025-12-20 11:32:13 - 上传线程结束: /mnt/save/warning/alarm_20251220_113211.jpg: 成功! +2025-12-20 11:32:14 - 开始上传图片: /mnt/save/warning/alarm_20251220_113213.jpg +2025-12-20 11:32:14 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:14 +2025-12-20 11:32:14 - 数据库插入成功 +2025-12-20 11:32:14 - 上传线程结束: /mnt/save/warning/alarm_20251220_113213.jpg: 成功! +2025-12-20 11:32:18 - 开始上传图片: /mnt/save/warning/alarm_20251220_113218.jpg +2025-12-20 11:32:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:18 +2025-12-20 11:32:19 - 数据库插入成功 +2025-12-20 11:32:19 - 上传线程结束: /mnt/save/warning/alarm_20251220_113218.jpg: 成功! +2025-12-20 11:32:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:32:24 - 开始上传图片: /mnt/save/warning/alarm_20251220_113223.jpg +2025-12-20 11:32:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:24 +2025-12-20 11:32:25 - 数据库插入成功 +2025-12-20 11:32:25 - 上传线程结束: /mnt/save/warning/alarm_20251220_113223.jpg: 成功! +2025-12-20 11:32:36 - 开始上传图片: /mnt/save/warning/alarm_20251220_113235.jpg +2025-12-20 11:32:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:36 +2025-12-20 11:32:37 - 数据库插入成功 +2025-12-20 11:32:37 - 上传线程结束: /mnt/save/warning/alarm_20251220_113235.jpg: 成功! +2025-12-20 11:32:40 - 开始上传图片: /mnt/save/warning/alarm_20251220_113240.jpg +2025-12-20 11:32:40 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:40 +2025-12-20 11:32:41 - 数据库插入成功 +2025-12-20 11:32:41 - 上传线程结束: /mnt/save/warning/alarm_20251220_113240.jpg: 成功! +2025-12-20 11:32:48 - 开始上传图片: /mnt/save/warning/alarm_20251220_113246.jpg +2025-12-20 11:32:48 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:48 +2025-12-20 11:32:49 - 数据库插入成功 +2025-12-20 11:32:49 - 上传线程结束: /mnt/save/warning/alarm_20251220_113246.jpg: 成功! +2025-12-20 11:32:51 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:32:52 - 开始上传图片: /mnt/save/warning/alarm_20251220_113252.jpg +2025-12-20 11:32:52 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:52 +2025-12-20 11:32:53 - 数据库插入成功 +2025-12-20 11:32:53 - 上传线程结束: /mnt/save/warning/alarm_20251220_113252.jpg: 成功! +2025-12-20 11:32:56 - 开始上传图片: /mnt/save/warning/alarm_20251220_113255.jpg +2025-12-20 11:32:56 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:32:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:32:56 +2025-12-20 11:32:57 - 数据库插入成功 +2025-12-20 11:32:57 - 上传线程结束: /mnt/save/warning/alarm_20251220_113255.jpg: 成功! +2025-12-20 11:33:04 - 开始上传图片: /mnt/save/warning/alarm_20251220_113302.jpg +2025-12-20 11:33:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:33:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:33:04 +2025-12-20 11:33:05 - 数据库插入成功 +2025-12-20 11:33:05 - 上传线程结束: /mnt/save/warning/alarm_20251220_113302.jpg: 成功! +2025-12-20 11:33:10 - 开始上传图片: /mnt/save/warning/alarm_20251220_113309.jpg +2025-12-20 11:33:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:33:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:33:10 +2025-12-20 11:33:11 - 数据库插入成功 +2025-12-20 11:33:11 - 上传线程结束: /mnt/save/warning/alarm_20251220_113309.jpg: 成功! +2025-12-20 11:33:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113312.jpg +2025-12-20 11:33:12 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:33:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:33:12 +2025-12-20 11:33:13 - 数据库插入成功 +2025-12-20 11:33:13 - 上传线程结束: /mnt/save/warning/alarm_20251220_113312.jpg: 成功! +2025-12-20 11:33:20 - 开始上传图片: /mnt/save/warning/alarm_20251220_113320.jpg +2025-12-20 11:33:20 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:33:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:33:20 +2025-12-20 11:33:21 - 读取ICCID异常: (5, 'Input/output error') +2025-12-20 11:33:21 - 数据库插入成功 +2025-12-20 11:33:21 - 上传线程结束: /mnt/save/warning/alarm_20251220_113320.jpg: 成功! +2025-12-20 11:33:24 - 开始上传图片: /mnt/save/warning/alarm_20251220_113324.jpg +2025-12-20 11:33:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:33:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:33:24 +2025-12-20 11:33:25 - 数据库插入成功 +2025-12-20 11:33:25 - 上传线程结束: /mnt/save/warning/alarm_20251220_113324.jpg: 成功! +2025-12-20 11:33:40 - 开始上传图片: /mnt/save/warning/alarm_20251220_113339.jpg +2025-12-20 11:33:40 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-20 11:33:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:33:40 +2025-12-20 11:33:41 - 数据库插入成功 +2025-12-20 11:33:41 - 上传线程结束: /mnt/save/warning/alarm_20251220_113339.jpg: 成功! +2025-12-20 11:17:11 - ICCID刷新线程启动 +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_111800.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112121.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112201.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112252.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112306.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112310.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112418.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112752.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112849.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113024.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113128.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113134.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113202.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113223.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113246.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113255.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113309.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113320.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113354.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113421.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113436.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113439.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113444.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_113459.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112039.jpg +2025-12-20 11:17:11 - 开始上传图片: /mnt/save/warning/alarm_20251220_112134.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112801.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112821.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112900.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113005.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113030.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113043.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113116.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113138.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113157.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113207.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113211.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113235.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113312.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113324.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113348.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113410.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113523.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112045.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112245.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112808.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112835.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112958.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113213.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113218.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113240.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113407.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113417.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113432.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113446.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113529.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113535.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113621.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112118.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112144.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112257.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112304.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_112447.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113008.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113017.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113035.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113155.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113252.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113302.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113339.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113455.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113509.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113605.jpg +2025-12-20 11:17:12 - 开始上传图片: /mnt/save/warning/alarm_20251220_113626.jpg +2025-12-20 11:17:18 - 读取ICCID成功: 898604581824D0366321 +2025-12-20 11:17:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:18 +2025-12-20 11:17:19 - 数据库插入成功 +2025-12-20 11:17:19 - 上传线程结束: /mnt/save/warning/alarm_20251220_113202.jpg: 成功! +2025-12-20 11:17:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:19 +2025-12-20 11:17:20 - 数据库插入成功 +2025-12-20 11:17:20 - 上传线程结束: /mnt/save/warning/alarm_20251220_111800.jpg: 成功! +2025-12-20 11:17:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:20 +2025-12-22 09:25:35 - 数据库插入成功 +2025-12-22 09:25:35 - 上传线程结束: /mnt/save/warning/alarm_20251220_112418.jpg: 成功! +2025-12-22 09:25:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:35 +2025-12-22 09:25:36 - 数据库插入成功 +2025-12-22 09:25:36 - 上传线程结束: /mnt/save/warning/alarm_20251220_112252.jpg: 成功! +2025-12-22 09:25:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:36 +2025-12-22 09:25:37 - 数据库插入成功 +2025-12-22 09:25:37 - 上传线程结束: /mnt/save/warning/alarm_20251220_112752.jpg: 成功! +2025-12-22 09:25:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:37 +2025-12-22 09:25:38 - 数据库插入成功 +2025-12-22 09:25:38 - 上传线程结束: /mnt/save/warning/alarm_20251220_112849.jpg: 成功! +2025-12-22 09:25:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:38 +2025-12-22 09:25:39 - 数据库插入成功 +2025-12-22 09:25:39 - 上传线程结束: /mnt/save/warning/alarm_20251220_112310.jpg: 成功! +2025-12-22 09:25:39 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:25:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:39 +2025-12-22 09:25:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:39 +2025-12-22 09:25:40 - 数据库插入成功 +2025-12-22 09:25:40 - 上传线程结束: /mnt/save/warning/alarm_20251220_113128.jpg: 成功! +2025-12-22 09:25:40 - 数据库插入成功 +2025-12-22 09:25:40 - 上传线程结束: /mnt/save/warning/alarm_20251220_113024.jpg: 成功! +2025-12-22 09:25:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:41 +2025-12-22 09:25:41 - 数据库插入成功 +2025-12-22 09:25:41 - 上传线程结束: /mnt/save/warning/alarm_20251220_113421.jpg: 成功! +2025-12-22 09:25:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:42 +2025-12-22 09:25:42 - 数据库插入成功 +2025-12-22 09:25:42 - 上传线程结束: /mnt/save/warning/alarm_20251220_113320.jpg: 成功! +2025-12-22 09:25:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:43 +2025-12-22 09:25:43 - 数据库插入成功 +2025-12-22 09:25:43 - 上传线程结束: /mnt/save/warning/alarm_20251220_113246.jpg: 成功! +2025-12-22 09:25:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:44 +2025-12-22 09:25:44 - 数据库插入成功 +2025-12-22 09:25:44 - 上传线程结束: /mnt/save/warning/alarm_20251220_113354.jpg: 成功! +2025-12-22 09:25:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:45 +2025-12-22 09:25:45 - 数据库插入成功 +2025-12-22 09:25:45 - 上传线程结束: /mnt/save/warning/alarm_20251220_113255.jpg: 成功! +2025-12-22 09:25:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:46 +2025-12-22 09:25:46 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:25:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:46 +2025-12-22 09:25:46 - 数据库插入成功 +2025-12-22 09:25:46 - 上传线程结束: /mnt/save/warning/alarm_20251220_113309.jpg: 成功! +2025-12-22 09:25:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:47 +2025-12-22 09:25:47 - 数据库插入成功 +2025-12-22 09:25:47 - 上传线程结束: /mnt/save/warning/alarm_20251220_112201.jpg: 成功! +2025-12-22 09:25:47 - 数据库插入成功 +2025-12-22 09:25:47 - 上传线程结束: /mnt/save/warning/alarm_20251220_113444.jpg: 成功! +2025-12-22 09:25:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:48 +2025-12-22 09:25:48 - 数据库插入成功 +2025-12-22 09:25:48 - 上传线程结束: /mnt/save/warning/alarm_20251220_113436.jpg: 成功! +2025-12-22 09:25:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:49 +2025-12-22 09:25:49 - 数据库插入成功 +2025-12-22 09:25:49 - 上传线程结束: /mnt/save/warning/alarm_20251220_113439.jpg: 成功! +2025-12-22 09:25:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:50 +2025-12-22 09:25:50 - 数据库插入成功 +2025-12-22 09:25:50 - 上传线程结束: /mnt/save/warning/alarm_20251220_113459.jpg: 成功! +2025-12-22 09:25:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:51 +2025-12-22 09:25:51 - 数据库插入成功 +2025-12-22 09:25:51 - 上传线程结束: /mnt/save/warning/alarm_20251220_112039.jpg: 成功! +2025-12-22 09:25:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:52 +2025-12-22 09:25:52 - 数据库插入成功 +2025-12-22 09:25:52 - 上传线程结束: /mnt/save/warning/alarm_20251220_112134.jpg: 成功! +2025-12-22 09:25:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:53 +2025-12-22 09:25:53 - 数据库插入成功 +2025-12-22 09:25:53 - 上传线程结束: /mnt/save/warning/alarm_20251220_112801.jpg: 成功! +2025-12-22 09:25:53 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:25:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:53 +2025-12-22 09:25:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:54 +2025-12-22 09:25:54 - 数据库插入成功 +2025-12-22 09:25:54 - 上传线程结束: /mnt/save/warning/alarm_20251220_112121.jpg: 成功! +2025-12-22 09:25:54 - 数据库插入成功 +2025-12-22 09:25:54 - 上传线程结束: /mnt/save/warning/alarm_20251220_112900.jpg: 成功! +2025-12-22 09:25:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:55 +2025-12-22 09:25:55 - 数据库插入成功 +2025-12-22 09:25:55 - 上传线程结束: /mnt/save/warning/alarm_20251220_112821.jpg: 成功! +2025-12-22 09:25:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:56 +2025-12-22 09:25:56 - 数据库插入成功 +2025-12-22 09:25:56 - 上传线程结束: /mnt/save/warning/alarm_20251220_113030.jpg: 成功! +2025-12-22 09:25:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:57 +2025-12-22 09:25:57 - 数据库插入成功 +2025-12-22 09:25:57 - 上传线程结束: /mnt/save/warning/alarm_20251220_113005.jpg: 成功! +2025-12-22 09:25:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:58 +2025-12-22 09:25:58 - 数据库插入成功 +2025-12-22 09:25:58 - 上传线程结束: /mnt/save/warning/alarm_20251220_113043.jpg: 成功! +2025-12-22 09:25:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:25:59 +2025-12-22 09:25:59 - 数据库插入成功 +2025-12-22 09:25:59 - 上传线程结束: /mnt/save/warning/alarm_20251220_113116.jpg: 成功! +2025-12-22 09:26:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:00 +2025-12-22 09:26:00 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:26:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:00 +2025-12-22 09:26:00 - 数据库插入成功 +2025-12-22 09:26:00 - 上传线程结束: /mnt/save/warning/alarm_20251220_113157.jpg: 成功! +2025-12-22 09:26:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:01 +2025-12-22 09:26:01 - 数据库插入成功 +2025-12-22 09:26:01 - 上传线程结束: /mnt/save/warning/alarm_20251220_112306.jpg: 成功! +2025-12-22 09:26:02 - 数据库插入成功 +2025-12-22 09:26:02 - 上传线程结束: /mnt/save/warning/alarm_20251220_113138.jpg: 成功! +2025-12-22 09:26:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:02 +2025-12-22 09:26:02 - 数据库插入成功 +2025-12-22 09:26:02 - 上传线程结束: /mnt/save/warning/alarm_20251220_113207.jpg: 成功! +2025-12-22 09:26:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:03 +2025-12-22 09:26:03 - 数据库插入成功 +2025-12-22 09:26:03 - 上传线程结束: /mnt/save/warning/alarm_20251220_113235.jpg: 成功! +2025-12-22 09:26:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:04 +2025-12-22 09:26:04 - 数据库插入成功 +2025-12-22 09:26:04 - 上传线程结束: /mnt/save/warning/alarm_20251220_113211.jpg: 成功! +2025-12-22 09:26:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:05 +2025-12-22 09:26:05 - 数据库插入成功 +2025-12-22 09:26:05 - 上传线程结束: /mnt/save/warning/alarm_20251220_112045.jpg: 成功! +2025-12-22 09:26:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:06 +2025-12-22 09:26:06 - 数据库插入成功 +2025-12-22 09:26:06 - 上传线程结束: /mnt/save/warning/alarm_20251220_112245.jpg: 成功! +2025-12-22 09:26:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:07 +2025-12-22 09:26:07 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:26:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:07 +2025-12-22 09:26:07 - 数据库插入成功 +2025-12-22 09:26:07 - 上传线程结束: /mnt/save/warning/alarm_20251220_113410.jpg: 成功! +2025-12-22 09:26:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:08 +2025-12-22 09:26:08 - 数据库插入成功 +2025-12-22 09:26:08 - 上传线程结束: /mnt/save/warning/alarm_20251220_112808.jpg: 成功! +2025-12-22 09:26:08 - 数据库插入成功 +2025-12-22 09:26:08 - 上传线程结束: /mnt/save/warning/alarm_20251220_113134.jpg: 成功! +2025-12-22 09:26:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:09 +2025-12-22 09:26:09 - 数据库插入成功 +2025-12-22 09:26:09 - 上传线程结束: /mnt/save/warning/alarm_20251220_113348.jpg: 成功! +2025-12-22 09:26:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:10 +2025-12-22 09:26:10 - 数据库插入成功 +2025-12-22 09:26:10 - 上传线程结束: /mnt/save/warning/alarm_20251220_113312.jpg: 成功! +2025-12-22 09:26:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:11 +2025-12-22 09:26:12 - 数据库插入成功 +2025-12-22 09:26:12 - 上传线程结束: /mnt/save/warning/alarm_20251220_112958.jpg: 成功! +2025-12-22 09:26:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:12 +2025-12-22 09:26:12 - 数据库插入成功 +2025-12-22 09:26:12 - 上传线程结束: /mnt/save/warning/alarm_20251220_113324.jpg: 成功! +2025-12-22 09:26:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:13 +2025-12-22 09:26:13 - 数据库插入成功 +2025-12-22 09:26:13 - 上传线程结束: /mnt/save/warning/alarm_20251220_113523.jpg: 成功! +2025-12-22 09:26:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:14 +2025-12-22 09:26:14 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:26:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:14 +2025-12-22 09:26:14 - 数据库插入成功 +2025-12-22 09:26:14 - 上传线程结束: /mnt/save/warning/alarm_20251220_113218.jpg: 成功! +2025-12-22 09:26:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:15 +2025-12-22 09:26:15 - 数据库插入成功 +2025-12-22 09:26:15 - 上传线程结束: /mnt/save/warning/alarm_20251220_113223.jpg: 成功! +2025-12-22 09:26:15 - 数据库插入成功 +2025-12-22 09:26:15 - 上传线程结束: /mnt/save/warning/alarm_20251220_113213.jpg: 成功! +2025-12-22 09:26:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:16 +2025-12-22 09:26:16 - 数据库插入成功 +2025-12-22 09:26:16 - 上传线程结束: /mnt/save/warning/alarm_20251220_112835.jpg: 成功! +2025-12-22 09:26:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:17 +2025-12-22 09:26:17 - 数据库插入成功 +2025-12-22 09:26:17 - 上传线程结束: /mnt/save/warning/alarm_20251220_113407.jpg: 成功! +2025-12-22 09:26:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:18 +2025-12-22 09:26:18 - 数据库插入成功 +2025-12-22 09:26:18 - 上传线程结束: /mnt/save/warning/alarm_20251220_113432.jpg: 成功! +2025-12-22 09:26:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:19 +2025-12-22 09:26:19 - 数据库插入成功 +2025-12-22 09:26:19 - 上传线程结束: /mnt/save/warning/alarm_20251220_113535.jpg: 成功! +2025-12-22 09:26:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:20 +2025-12-22 09:26:20 - 数据库插入成功 +2025-12-22 09:26:20 - 上传线程结束: /mnt/save/warning/alarm_20251220_113240.jpg: 成功! +2025-12-22 09:26:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:21 +2025-12-22 09:26:21 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:26:21 - 数据库插入成功 +2025-12-22 09:26:21 - 上传线程结束: /mnt/save/warning/alarm_20251220_113417.jpg: 成功! +2025-12-22 09:26:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:22 +2025-12-22 09:26:22 - 数据库插入成功 +2025-12-22 09:26:22 - 上传线程结束: /mnt/save/warning/alarm_20251220_113621.jpg: 成功! +2025-12-22 09:26:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:23 +2025-12-22 09:26:24 - 数据库插入成功 +2025-12-22 09:26:24 - 上传线程结束: /mnt/save/warning/alarm_20251220_113529.jpg: 成功! +2025-12-22 09:26:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:24 +2025-12-22 09:26:25 - 数据库插入成功 +2025-12-22 09:26:25 - 上传线程结束: /mnt/save/warning/alarm_20251220_113446.jpg: 成功! +2025-12-22 09:26:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:25 +2025-12-22 09:26:25 - 数据库插入成功 +2025-12-22 09:26:25 - 上传线程结束: /mnt/save/warning/alarm_20251220_112144.jpg: 成功! +2025-12-22 09:26:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:26 +2025-12-22 09:26:26 - 数据库插入成功 +2025-12-22 09:26:26 - 上传线程结束: /mnt/save/warning/alarm_20251220_113509.jpg: 成功! +2025-12-22 09:26:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:27 +2025-12-22 09:26:27 - 数据库插入成功 +2025-12-22 09:26:27 - 上传线程结束: /mnt/save/warning/alarm_20251220_113008.jpg: 成功! +2025-12-22 09:26:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:28 +2025-12-22 09:26:28 - 数据库插入成功 +2025-12-22 09:26:28 - 上传线程结束: /mnt/save/warning/alarm_20251220_113252.jpg: 成功! +2025-12-22 09:26:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:29 +2025-12-22 09:26:30 - 数据库插入成功 +2025-12-22 09:26:30 - 上传线程结束: /mnt/save/warning/alarm_20251220_113455.jpg: 成功! +2025-12-22 09:26:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:30 +2025-12-22 09:26:30 - 数据库插入成功 +2025-12-22 09:26:30 - 上传线程结束: /mnt/save/warning/alarm_20251220_113035.jpg: 成功! +2025-12-22 09:26:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:31 +2025-12-22 09:26:31 - 数据库插入成功 +2025-12-22 09:26:31 - 上传线程结束: /mnt/save/warning/alarm_20251220_112447.jpg: 成功! +2025-12-22 09:26:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:26:32 +2025-12-22 09:26:32 - 数据库插入成功 +2025-12-22 09:26:32 - 上传线程结束: /mnt/save/warning/alarm_20251220_112257.jpg: 成功! +2025-12-20 11:17:09 - ICCID刷新线程启动 +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_111659.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_111800.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112121.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112201.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112252.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112306.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112310.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112418.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112752.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112849.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113024.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113128.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113134.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113202.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113223.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113246.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113255.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113309.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113320.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113354.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113421.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113436.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113439.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113444.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113459.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112039.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112134.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112801.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112821.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112900.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113005.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113030.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113043.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113116.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113138.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113157.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113207.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113211.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113235.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113312.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113324.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113348.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113410.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113523.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112045.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112245.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112808.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112835.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112958.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113213.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113218.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113240.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113407.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113417.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113432.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113446.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113529.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113535.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113621.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112118.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112144.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112257.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112304.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112447.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113008.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113017.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113035.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113155.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113252.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113302.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113339.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113455.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113509.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113605.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113626.jpg +2025-12-20 11:17:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-20 11:17:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:16 +2025-12-20 11:17:17 - 数据库插入成功 +2025-12-20 11:17:17 - 上传线程结束: /mnt/save/warning/alarm_20251220_113024.jpg: 成功! +2025-12-20 11:17:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:17 +2025-12-20 11:17:18 - 数据库插入成功 +2025-12-20 11:17:18 - 上传线程结束: /mnt/save/warning/alarm_20251220_111659.jpg: 成功! +2025-12-20 11:17:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:18 +2025-12-20 11:17:19 - 数据库插入成功 +2025-12-20 11:17:19 - 上传线程结束: /mnt/save/warning/alarm_20251220_112849.jpg: 成功! +2025-12-20 11:17:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:19 +2025-12-22 09:27:25 - 数据库插入成功 +2025-12-22 09:27:25 - 上传线程结束: /mnt/save/warning/alarm_20251220_112252.jpg: 成功! +2025-12-22 09:27:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:25 +2025-12-22 09:27:26 - 数据库插入成功 +2025-12-22 09:27:26 - 上传线程结束: /mnt/save/warning/alarm_20251220_112201.jpg: 成功! +2025-12-22 09:27:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:26 +2025-12-22 09:27:27 - 数据库插入成功 +2025-12-22 09:27:27 - 上传线程结束: /mnt/save/warning/alarm_20251220_113128.jpg: 成功! +2025-12-22 09:27:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:27 +2025-12-22 09:27:28 - 数据库插入成功 +2025-12-22 09:27:28 - 上传线程结束: /mnt/save/warning/alarm_20251220_113202.jpg: 成功! +2025-12-22 09:27:28 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:27:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:28 +2025-12-22 09:27:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:28 +2025-12-22 09:27:29 - 数据库插入成功 +2025-12-22 09:27:29 - 上传线程结束: /mnt/save/warning/alarm_20251220_111800.jpg: 成功! +2025-12-22 09:27:29 - 数据库插入成功 +2025-12-22 09:27:29 - 上传线程结束: /mnt/save/warning/alarm_20251220_112752.jpg: 成功! +2025-12-22 09:27:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:29 +2025-12-22 09:27:30 - 数据库插入成功 +2025-12-22 09:27:30 - 上传线程结束: /mnt/save/warning/alarm_20251220_113134.jpg: 成功! +2025-12-22 09:27:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:30 +2025-12-22 09:27:31 - 数据库插入成功 +2025-12-22 09:27:31 - 上传线程结束: /mnt/save/warning/alarm_20251220_113246.jpg: 成功! +2025-12-22 09:27:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:31 +2025-12-22 09:27:32 - 数据库插入成功 +2025-12-22 09:27:32 - 上传线程结束: /mnt/save/warning/alarm_20251220_113421.jpg: 成功! +2025-12-22 09:27:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:32 +2025-12-22 09:27:33 - 数据库插入成功 +2025-12-22 09:27:33 - 上传线程结束: /mnt/save/warning/alarm_20251220_113255.jpg: 成功! +2025-12-22 09:27:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:33 +2025-12-22 09:27:34 - 数据库插入成功 +2025-12-22 09:27:34 - 上传线程结束: /mnt/save/warning/alarm_20251220_113354.jpg: 成功! +2025-12-22 09:27:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:34 +2025-12-22 09:27:35 - 数据库插入成功 +2025-12-22 09:27:35 - 上传线程结束: /mnt/save/warning/alarm_20251220_113309.jpg: 成功! +2025-12-22 09:27:35 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:27:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:35 +2025-12-22 09:27:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:35 +2025-12-22 09:27:36 - 数据库插入成功 +2025-12-22 09:27:36 - 上传线程结束: /mnt/save/warning/alarm_20251220_112121.jpg: 成功! +2025-12-22 09:27:36 - 数据库插入成功 +2025-12-22 09:27:36 - 上传线程结束: /mnt/save/warning/alarm_20251220_113320.jpg: 成功! +2025-12-22 09:27:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:36 +2025-12-22 09:27:37 - 数据库插入成功 +2025-12-22 09:27:37 - 上传线程结束: /mnt/save/warning/alarm_20251220_113436.jpg: 成功! +2025-12-22 09:27:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:37 +2025-12-22 09:27:38 - 数据库插入成功 +2025-12-22 09:27:38 - 上传线程结束: /mnt/save/warning/alarm_20251220_113439.jpg: 成功! +2025-12-22 09:27:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:38 +2025-12-22 09:27:39 - 数据库插入成功 +2025-12-22 09:27:39 - 上传线程结束: /mnt/save/warning/alarm_20251220_113138.jpg: 成功! +2025-12-22 09:27:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:39 +2025-12-22 09:27:40 - 数据库插入成功 +2025-12-22 09:27:40 - 上传线程结束: /mnt/save/warning/alarm_20251220_112821.jpg: 成功! +2025-12-22 09:27:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:40 +2025-12-22 09:27:41 - 数据库插入成功 +2025-12-22 09:27:41 - 上传线程结束: /mnt/save/warning/alarm_20251220_113116.jpg: 成功! +2025-12-22 09:27:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:41 +2025-12-22 09:27:42 - 数据库插入成功 +2025-12-22 09:27:42 - 上传线程结束: /mnt/save/warning/alarm_20251220_113444.jpg: 成功! +2025-12-22 09:27:42 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:27:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:42 +2025-12-22 09:27:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:42 +2025-12-22 09:27:43 - 数据库插入成功 +2025-12-22 09:27:43 - 上传线程结束: /mnt/save/warning/alarm_20251220_112310.jpg: 成功! +2025-12-22 09:27:43 - 数据库插入成功 +2025-12-22 09:27:43 - 上传线程结束: /mnt/save/warning/alarm_20251220_113235.jpg: 成功! +2025-12-22 09:27:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:43 +2025-12-22 09:27:44 - 数据库插入成功 +2025-12-22 09:27:44 - 上传线程结束: /mnt/save/warning/alarm_20251220_113459.jpg: 成功! +2025-12-22 09:27:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:44 +2025-12-22 09:27:45 - 开始上传图片: /mnt/save/warning/alarm_20251222_092743.jpg +2025-12-22 09:27:45 - 数据库插入成功 +2025-12-22 09:27:45 - 上传线程结束: /mnt/save/warning/alarm_20251220_112801.jpg: 成功! +2025-12-22 09:27:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:45 +2025-12-22 09:27:46 - 数据库插入成功 +2025-12-22 09:27:46 - 上传线程结束: /mnt/save/warning/alarm_20251220_113005.jpg: 成功! +2025-12-22 09:27:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:46 +2025-12-22 09:27:47 - 数据库插入成功 +2025-12-22 09:27:47 - 上传线程结束: /mnt/save/warning/alarm_20251220_112134.jpg: 成功! +2025-12-22 09:27:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:47 +2025-12-22 09:27:48 - 数据库插入成功 +2025-12-22 09:27:48 - 上传线程结束: /mnt/save/warning/alarm_20251220_112900.jpg: 成功! +2025-12-22 09:27:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:48 +2025-12-22 09:27:49 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:27:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:49 +2025-12-22 09:27:49 - 数据库插入成功 +2025-12-22 09:27:49 - 上传线程结束: /mnt/save/warning/alarm_20251220_112039.jpg: 成功! +2025-12-22 09:27:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:49 +2025-12-22 09:27:50 - 数据库插入成功 +2025-12-22 09:27:50 - 上传线程结束: /mnt/save/warning/alarm_20251220_112306.jpg: 成功! +2025-12-22 09:27:50 - 数据库插入成功 +2025-12-22 09:27:50 - 上传线程结束: /mnt/save/warning/alarm_20251220_113312.jpg: 成功! +2025-12-22 09:27:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:50 +2025-12-22 09:27:51 - 数据库插入成功 +2025-12-22 09:27:51 - 上传线程结束: /mnt/save/warning/alarm_20251220_113043.jpg: 成功! +2025-12-22 09:27:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:51 +2025-12-22 09:27:52 - 数据库插入成功 +2025-12-22 09:27:52 - 上传线程结束: /mnt/save/warning/alarm_20251220_113030.jpg: 成功! +2025-12-22 09:27:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:52 +2025-12-22 09:27:53 - 数据库插入成功 +2025-12-22 09:27:53 - 上传线程结束: /mnt/save/warning/alarm_20251220_112045.jpg: 成功! +2025-12-22 09:27:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:53 +2025-12-22 09:27:54 - 数据库插入成功 +2025-12-22 09:27:54 - 上传线程结束: /mnt/save/warning/alarm_20251220_112835.jpg: 成功! +2025-12-22 09:27:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:54 +2025-12-22 09:27:55 - 数据库插入成功 +2025-12-22 09:27:55 - 上传线程结束: /mnt/save/warning/alarm_20251220_113324.jpg: 成功! +2025-12-22 09:27:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:55 +2025-12-22 09:27:56 - 数据库插入成功 +2025-12-22 09:27:56 - 上传线程结束: /mnt/save/warning/alarm_20251220_113410.jpg: 成功! +2025-12-22 09:27:56 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:27:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:56 +2025-12-22 09:27:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:56 +2025-12-22 09:27:57 - 数据库插入成功 +2025-12-22 09:27:57 - 上传线程结束: /mnt/save/warning/alarm_20251220_112418.jpg: 成功! +2025-12-22 09:27:57 - 数据库插入成功 +2025-12-22 09:27:57 - 上传线程结束: /mnt/save/warning/alarm_20251220_113523.jpg: 成功! +2025-12-22 09:27:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:57 +2025-12-22 09:27:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:58 +2025-12-22 09:27:58 - 数据库插入成功 +2025-12-22 09:27:58 - 上传线程结束: /mnt/save/warning/alarm_20251220_113348.jpg: 成功! +2025-12-22 09:27:59 - 数据库插入成功 +2025-12-22 09:27:59 - 上传线程结束: /mnt/save/warning/alarm_20251220_113211.jpg: 成功! +2025-12-22 09:27:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:27:59 +2025-12-22 09:28:00 - 数据库插入成功 +2025-12-22 09:28:00 - 上传线程结束: /mnt/save/warning/alarm_20251220_113157.jpg: 成功! +2025-12-22 09:28:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:00 +2025-12-22 09:28:01 - 数据库插入成功 +2025-12-22 09:28:01 - 上传线程结束: /mnt/save/warning/alarm_20251220_113207.jpg: 成功! +2025-12-22 09:28:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:01 +2025-12-22 09:28:02 - 数据库插入成功 +2025-12-22 09:28:02 - 上传线程结束: /mnt/save/warning/alarm_20251220_112808.jpg: 成功! +2025-12-22 09:28:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:02 +2025-12-22 09:28:03 - 数据库插入成功 +2025-12-22 09:28:03 - 上传线程结束: /mnt/save/warning/alarm_20251220_112245.jpg: 成功! +2025-12-22 09:28:03 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 09:28:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:03 +2025-12-22 09:28:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:03 +2025-12-22 09:28:04 - 数据库插入成功 +2025-12-22 09:28:04 - 上传线程结束: /mnt/save/warning/alarm_20251220_113218.jpg: 成功! +2025-12-22 09:28:04 - 数据库插入成功 +2025-12-22 09:28:04 - 上传线程结束: /mnt/save/warning/alarm_20251220_113223.jpg: 成功! +2025-12-22 09:28:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:04 +2025-12-22 09:28:05 - 数据库插入成功 +2025-12-22 09:28:05 - 上传线程结束: /mnt/save/warning/alarm_20251220_113407.jpg: 成功! +2025-12-22 09:28:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:05 +2025-12-22 09:28:06 - 数据库插入成功 +2025-12-22 09:28:06 - 上传线程结束: /mnt/save/warning/alarm_20251220_112958.jpg: 成功! +2025-12-22 09:28:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:06 +2025-12-22 09:28:07 - 数据库插入成功 +2025-12-22 09:28:07 - 上传线程结束: /mnt/save/warning/alarm_20251220_113213.jpg: 成功! +2025-12-22 09:28:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:07 +2025-12-22 09:28:08 - 数据库插入成功 +2025-12-22 09:28:08 - 上传线程结束: /mnt/save/warning/alarm_20251220_113529.jpg: 成功! +2025-12-22 09:28:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:08 +2025-12-22 09:28:09 - 数据库插入成功 +2025-12-22 09:28:09 - 上传线程结束: /mnt/save/warning/alarm_20251220_113432.jpg: 成功! +2025-12-22 09:28:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:09 +2025-12-22 09:28:10 - 读取ICCID异常: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:10 +2025-12-22 09:28:11 - 数据库插入成功 +2025-12-22 09:28:11 - 上传线程结束: /mnt/save/warning/alarm_20251220_113535.jpg: 成功! +2025-12-22 09:28:11 - 开始上传图片: /mnt/save/warning/alarm_20251222_092811.jpg +2025-12-22 09:28:11 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 09:28:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 09:28:11 +2025-12-22 09:28:12 - 数据库插入成功 +2025-12-22 09:28:12 - 上传线程结束: /mnt/save/warning/alarm_20251222_092811.jpg: 成功! +2025-12-22 09:28:12 - 数据库插入成功 +2025-12-22 09:28:12 - 上传线程结束: /mnt/save/warning/alarm_20251220_113417.jpg: 成功! +2025-12-22 09:28:15 - 数据库插入成功 +2025-12-22 09:28:15 - 上传线程结束: /mnt/save/warning/alarm_20251220_112144.jpg: 成功! +2025-12-22 09:28:15 - 数据库插入成功 +2025-12-22 09:28:15 - 上传线程结束: /mnt/save/warning/alarm_20251220_113017.jpg: 成功! +2025-12-22 09:28:15 - 数据库插入成功 +2025-12-22 09:28:15 - 上传线程结束: /mnt/save/warning/alarm_20251220_113008.jpg: 成功! +2025-12-20 11:17:08 - ICCID刷新线程启动 +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_111659.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_111800.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112121.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112201.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112252.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112306.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112310.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112418.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112752.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112849.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113024.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113128.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113134.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113202.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113223.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113246.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113255.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113309.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113320.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113354.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113421.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113436.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113439.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113444.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113459.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251222_092743.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112039.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112134.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112801.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112821.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112900.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113005.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113030.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113043.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113116.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113138.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113157.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113207.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113211.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113235.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113312.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113324.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113348.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113410.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113523.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251222_092811.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112045.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112245.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112808.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112835.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112958.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113213.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113218.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113240.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113407.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113417.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113432.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113446.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113529.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113535.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113621.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112118.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112144.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112257.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112304.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_112447.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113008.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113017.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113035.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113155.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113252.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113302.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113339.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113455.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113509.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113605.jpg +2025-12-20 11:17:09 - 开始上传图片: /mnt/save/warning/alarm_20251220_113626.jpg +2025-12-20 11:17:16 - 读取ICCID成功: 898604581824D0366321 +2025-12-20 11:17:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:16 +2025-12-20 11:17:16 - 数据库插入成功 +2025-12-20 11:17:16 - 上传线程结束: /mnt/save/warning/alarm_20251220_112306.jpg: 成功! +2025-12-20 11:17:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:17 +2025-12-20 11:17:18 - 数据库插入成功 +2025-12-20 11:17:18 - 上传线程结束: /mnt/save/warning/alarm_20251220_111800.jpg: 成功! +2025-12-20 11:17:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:18 +2025-12-20 11:17:18 - 数据库插入成功 +2025-12-20 11:17:18 - 上传线程结束: /mnt/save/warning/alarm_20251220_113255.jpg: 成功! +2025-12-20 11:17:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-20 11:17:19 +2025-12-22 12:11:09 - 数据库插入成功 +2025-12-22 12:11:09 - 上传线程结束: /mnt/save/warning/alarm_20251220_113309.jpg: 成功! +2025-12-22 12:11:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:10 +2025-12-22 12:11:10 - 数据库插入成功 +2025-12-22 12:11:10 - 上传线程结束: /mnt/save/warning/alarm_20251220_113436.jpg: 成功! +2025-12-22 12:11:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:11 +2025-12-22 12:11:11 - 数据库插入成功 +2025-12-22 12:11:11 - 上传线程结束: /mnt/save/warning/alarm_20251220_113134.jpg: 成功! +2025-12-22 12:11:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:12 +2025-12-22 12:11:12 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 12:11:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:12 +2025-12-22 12:11:12 - 数据库插入成功 +2025-12-22 12:11:12 - 上传线程结束: /mnt/save/warning/alarm_20251220_113421.jpg: 成功! +2025-12-22 12:11:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:13 +2025-12-22 12:11:13 - 数据库插入成功 +2025-12-22 12:11:13 - 上传线程结束: /mnt/save/warning/alarm_20251220_112418.jpg: 成功! +2025-12-22 12:11:13 - 数据库插入成功 +2025-12-22 12:11:13 - 上传线程结束: /mnt/save/warning/alarm_20251220_113459.jpg: 成功! +2025-12-22 12:11:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:14 +2025-12-22 12:11:14 - 数据库插入成功 +2025-12-22 12:11:14 - 上传线程结束: /mnt/save/warning/alarm_20251220_113354.jpg: 成功! +2025-12-22 12:11:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:15 +2025-12-22 12:11:15 - 数据库插入成功 +2025-12-22 12:11:15 - 上传线程结束: /mnt/save/warning/alarm_20251220_112310.jpg: 成功! +2025-12-22 12:11:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:16 +2025-12-22 12:11:16 - 数据库插入成功 +2025-12-22 12:11:16 - 上传线程结束: /mnt/save/warning/alarm_20251220_113202.jpg: 成功! +2025-12-22 12:11:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:17 +2025-12-22 12:11:17 - 开始上传图片: /mnt/save/warning/alarm_20251222_121116.jpg +2025-12-22 12:11:17 - 数据库插入成功 +2025-12-22 12:11:17 - 上传线程结束: /mnt/save/warning/alarm_20251220_113320.jpg: 成功! +2025-12-22 12:11:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:18 +2025-12-22 12:11:18 - 数据库插入成功 +2025-12-22 12:11:18 - 上传线程结束: /mnt/save/warning/alarm_20251222_092743.jpg: 成功! +2025-12-22 12:11:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:19 +2025-12-22 12:11:19 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 12:11:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:19 +2025-12-22 12:11:19 - 数据库插入成功 +2025-12-22 12:11:19 - 上传线程结束: /mnt/save/warning/alarm_20251220_113128.jpg: 成功! +2025-12-22 12:11:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:20 +2025-12-22 12:11:20 - 数据库插入成功 +2025-12-22 12:11:20 - 上传线程结束: /mnt/save/warning/alarm_20251220_112201.jpg: 成功! +2025-12-22 12:11:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:21 +2025-12-22 12:11:21 - 数据库插入成功 +2025-12-22 12:11:21 - 上传线程结束: /mnt/save/warning/alarm_20251220_112752.jpg: 成功! +2025-12-22 12:11:21 - 数据库插入成功 +2025-12-22 12:11:21 - 上传线程结束: /mnt/save/warning/alarm_20251220_112821.jpg: 成功! +2025-12-22 12:11:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:22 +2025-12-22 12:11:22 - 数据库插入成功 +2025-12-22 12:11:22 - 上传线程结束: /mnt/save/warning/alarm_20251220_113444.jpg: 成功! +2025-12-22 12:11:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:23 +2025-12-22 12:11:23 - 数据库插入成功 +2025-12-22 12:11:23 - 上传线程结束: /mnt/save/warning/alarm_20251220_113246.jpg: 成功! +2025-12-22 12:11:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:24 +2025-12-22 12:11:25 - 数据库插入成功 +2025-12-22 12:11:25 - 上传线程结束: /mnt/save/warning/alarm_20251220_113223.jpg: 成功! +2025-12-22 12:11:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:25 +2025-12-22 12:11:25 - 数据库插入成功 +2025-12-22 12:11:25 - 上传线程结束: /mnt/save/warning/alarm_20251220_113439.jpg: 成功! +2025-12-22 12:11:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:26 +2025-12-22 12:11:26 - 数据库插入成功 +2025-12-22 12:11:26 - 上传线程结束: /mnt/save/warning/alarm_20251220_113024.jpg: 成功! +2025-12-22 12:11:26 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 12:11:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:26 +2025-12-22 12:11:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:27 +2025-12-22 12:11:27 - 数据库插入成功 +2025-12-22 12:11:27 - 上传线程结束: /mnt/save/warning/alarm_20251220_112801.jpg: 成功! +2025-12-22 12:11:27 - 数据库插入成功 +2025-12-22 12:11:27 - 上传线程结束: /mnt/save/warning/alarm_20251220_112252.jpg: 成功! +2025-12-22 12:11:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:28 +2025-12-22 12:11:28 - 数据库插入成功 +2025-12-22 12:11:28 - 上传线程结束: /mnt/save/warning/alarm_20251220_113138.jpg: 成功! +2025-12-22 12:11:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:29 +2025-12-22 12:11:29 - 数据库插入成功 +2025-12-22 12:11:29 - 上传线程结束: /mnt/save/warning/alarm_20251220_112900.jpg: 成功! +2025-12-22 12:11:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:30 +2025-12-22 12:11:30 - 数据库插入成功 +2025-12-22 12:11:30 - 上传线程结束: /mnt/save/warning/alarm_20251220_113030.jpg: 成功! +2025-12-22 12:11:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:31 +2025-12-22 12:11:31 - 数据库插入成功 +2025-12-22 12:11:31 - 上传线程结束: /mnt/save/warning/alarm_20251220_113207.jpg: 成功! +2025-12-22 12:11:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:32 +2025-12-22 12:11:32 - 数据库插入成功 +2025-12-22 12:11:32 - 上传线程结束: /mnt/save/warning/alarm_20251220_113005.jpg: 成功! +2025-12-22 12:11:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:33 +2025-12-22 12:11:33 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 12:11:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:33 +2025-12-22 12:11:33 - 数据库插入成功 +2025-12-22 12:11:33 - 上传线程结束: /mnt/save/warning/alarm_20251220_112134.jpg: 成功! +2025-12-22 12:11:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:34 +2025-12-22 12:11:34 - 数据库插入成功 +2025-12-22 12:11:34 - 上传线程结束: /mnt/save/warning/alarm_20251220_112121.jpg: 成功! +2025-12-22 12:11:34 - 数据库插入成功 +2025-12-22 12:11:34 - 上传线程结束: /mnt/save/warning/alarm_20251220_112039.jpg: 成功! +2025-12-22 12:11:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:35 +2025-12-22 12:11:35 - 数据库插入成功 +2025-12-22 12:11:35 - 上传线程结束: /mnt/save/warning/alarm_20251220_113043.jpg: 成功! +2025-12-22 12:11:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:36 +2025-12-22 12:11:37 - 数据库插入成功 +2025-12-22 12:11:37 - 上传线程结束: /mnt/save/warning/alarm_20251220_113116.jpg: 成功! +2025-12-22 12:11:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:37 +2025-12-22 12:11:37 - 数据库插入成功 +2025-12-22 12:11:37 - 上传线程结束: /mnt/save/warning/alarm_20251220_113211.jpg: 成功! +2025-12-22 12:11:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:38 +2025-12-22 12:11:38 - 数据库插入成功 +2025-12-22 12:11:38 - 上传线程结束: /mnt/save/warning/alarm_20251222_092811.jpg: 成功! +2025-12-22 12:11:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:39 +2025-12-22 12:11:39 - 数据库插入成功 +2025-12-22 12:11:39 - 上传线程结束: /mnt/save/warning/alarm_20251220_113157.jpg: 成功! +2025-12-22 12:11:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:40 +2025-12-22 12:11:40 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 12:11:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:40 +2025-12-22 12:11:40 - 数据库插入成功 +2025-12-22 12:11:40 - 上传线程结束: /mnt/save/warning/alarm_20251220_113235.jpg: 成功! +2025-12-22 12:11:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:41 +2025-12-22 12:11:41 - 数据库插入成功 +2025-12-22 12:11:41 - 上传线程结束: /mnt/save/warning/alarm_20251220_111659.jpg: 成功! +2025-12-22 12:11:41 - 数据库插入成功 +2025-12-22 12:11:41 - 上传线程结束: /mnt/save/warning/alarm_20251220_113324.jpg: 成功! +2025-12-22 12:11:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:42 +2025-12-22 12:11:42 - 数据库插入成功 +2025-12-22 12:11:42 - 上传线程结束: /mnt/save/warning/alarm_20251220_113348.jpg: 成功! +2025-12-22 12:11:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:43 +2025-12-22 12:11:43 - 数据库插入成功 +2025-12-22 12:11:43 - 上传线程结束: /mnt/save/warning/alarm_20251220_112245.jpg: 成功! +2025-12-22 12:11:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:44 +2025-12-22 12:11:44 - 数据库插入成功 +2025-12-22 12:11:44 - 上传线程结束: /mnt/save/warning/alarm_20251220_113523.jpg: 成功! +2025-12-22 12:11:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:45 +2025-12-22 12:11:45 - 数据库插入成功 +2025-12-22 12:11:46 - 上传线程结束: /mnt/save/warning/alarm_20251220_113312.jpg: 成功! +2025-12-22 12:11:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:46 +2025-12-22 12:11:46 - 数据库插入成功 +2025-12-22 12:11:46 - 上传线程结束: /mnt/save/warning/alarm_20251220_113410.jpg: 成功! +2025-12-22 12:11:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:47 +2025-12-22 12:11:47 - 开始上传图片: /mnt/save/warning/alarm_20251222_121146.jpg +2025-12-22 12:11:47 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 12:11:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:47 +2025-12-22 12:11:48 - 数据库插入成功 +2025-12-22 12:11:48 - 上传线程结束: /mnt/save/warning/alarm_20251220_112808.jpg: 成功! +2025-12-22 12:11:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:48 +2025-12-22 12:11:48 - 数据库插入成功 +2025-12-22 12:11:48 - 上传线程结束: /mnt/save/warning/alarm_20251220_112849.jpg: 成功! +2025-12-22 12:11:48 - 数据库插入成功 +2025-12-22 12:11:48 - 上传线程结束: /mnt/save/warning/alarm_20251220_112045.jpg: 成功! +2025-12-22 12:11:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:49 +2025-12-22 12:11:49 - 开始上传图片: /mnt/save/warning/alarm_20251222_121148.jpg +2025-12-22 12:11:49 - 数据库插入成功 +2025-12-22 12:11:49 - 上传线程结束: /mnt/save/warning/alarm_20251220_112835.jpg: 成功! +2025-12-22 12:11:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:50 +2025-12-22 12:11:50 - 数据库插入成功 +2025-12-22 12:11:50 - 上传线程结束: /mnt/save/warning/alarm_20251220_113407.jpg: 成功! +2025-12-22 12:11:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:51 +2025-12-22 12:11:51 - 数据库插入成功 +2025-12-22 12:11:51 - 上传线程结束: /mnt/save/warning/alarm_20251220_113417.jpg: 成功! +2025-12-22 12:11:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:52 +2025-12-22 12:11:52 - 数据库插入成功 +2025-12-22 12:11:52 - 上传线程结束: /mnt/save/warning/alarm_20251220_112958.jpg: 成功! +2025-12-22 12:11:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:53 +2025-12-22 12:11:54 - 数据库插入成功 +2025-12-22 12:11:54 - 上传线程结束: /mnt/save/warning/alarm_20251220_113446.jpg: 成功! +2025-12-22 12:11:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:54 +2025-12-22 12:11:54 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 12:11:54 - 数据库插入成功 +2025-12-22 12:11:54 - 上传线程结束: /mnt/save/warning/alarm_20251220_113535.jpg: 成功! +2025-12-22 12:11:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:55 +2025-12-22 12:11:56 - 数据库插入成功 +2025-12-22 12:11:56 - 上传线程结束: /mnt/save/warning/alarm_20251220_113240.jpg: 成功! +2025-12-22 12:11:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:56 +2025-12-22 12:11:57 - 数据库插入成功 +2025-12-22 12:11:57 - 上传线程结束: /mnt/save/warning/alarm_20251220_112118.jpg: 成功! +2025-12-22 12:11:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:57 +2025-12-22 12:11:57 - 数据库插入成功 +2025-12-22 12:11:57 - 上传线程结束: /mnt/save/warning/alarm_20251220_113213.jpg: 成功! +2025-12-22 12:11:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:58 +2025-12-22 12:11:58 - 数据库插入成功 +2025-12-22 12:11:58 - 上传线程结束: /mnt/save/warning/alarm_20251220_112144.jpg: 成功! +2025-12-22 12:11:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:11:59 +2025-12-22 12:11:59 - 数据库插入成功 +2025-12-22 12:11:59 - 上传线程结束: /mnt/save/warning/alarm_20251220_113218.jpg: 成功! +2025-12-22 12:12:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:00 +2025-12-22 12:12:00 - 数据库插入成功 +2025-12-22 12:12:00 - 上传线程结束: /mnt/save/warning/alarm_20251220_112257.jpg: 成功! +2025-12-22 12:12:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:01 +2025-12-22 12:12:02 - 数据库插入成功 +2025-12-22 12:12:02 - 上传线程结束: /mnt/save/warning/alarm_20251220_112447.jpg: 成功! +2025-12-22 12:12:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:02 +2025-12-22 12:12:02 - 数据库插入成功 +2025-12-22 12:12:02 - 上传线程结束: /mnt/save/warning/alarm_20251220_113605.jpg: 成功! +2025-12-22 12:12:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:03 +2025-12-22 12:12:03 - 数据库插入成功 +2025-12-22 12:12:03 - 上传线程结束: /mnt/save/warning/alarm_20251220_113626.jpg: 成功! +2025-12-22 12:12:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:04 +2025-12-22 12:12:04 - 数据库插入成功 +2025-12-22 12:12:04 - 上传线程结束: /mnt/save/warning/alarm_20251220_113155.jpg: 成功! +2025-12-22 12:12:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:05 +2025-12-22 12:12:06 - 数据库插入成功 +2025-12-22 12:12:06 - 上传线程结束: /mnt/save/warning/alarm_20251220_113509.jpg: 成功! +2025-12-22 12:12:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:06 +2025-12-22 12:12:06 - 数据库插入成功 +2025-12-22 12:12:06 - 上传线程结束: /mnt/save/warning/alarm_20251220_113252.jpg: 成功! +2025-12-22 12:12:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:07 +2025-12-22 12:12:07 - 数据库插入成功 +2025-12-22 12:12:07 - 上传线程结束: /mnt/save/warning/alarm_20251220_113432.jpg: 成功! +2025-12-22 12:12:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:08 +2025-12-22 12:12:08 - 数据库插入成功 +2025-12-22 12:12:09 - 上传线程结束: /mnt/save/warning/alarm_20251220_113621.jpg: 成功! +2025-12-22 12:12:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:09 +2025-12-22 12:12:10 - 数据库插入成功 +2025-12-22 12:12:10 - 上传线程结束: /mnt/save/warning/alarm_20251220_113529.jpg: 成功! +2025-12-22 12:12:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:10 +2025-12-22 12:12:10 - 数据库插入成功 +2025-12-22 12:12:10 - 上传线程结束: /mnt/save/warning/alarm_20251220_113008.jpg: 成功! +2025-12-22 12:12:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:11 +2025-12-22 12:12:11 - 数据库插入成功 +2025-12-22 12:12:11 - 上传线程结束: /mnt/save/warning/alarm_20251220_113339.jpg: 成功! +2025-12-22 12:12:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:12 +2025-12-22 12:12:12 - 数据库插入成功 +2025-12-22 12:12:12 - 上传线程结束: /mnt/save/warning/alarm_20251220_112304.jpg: 成功! +2025-12-22 12:12:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:13 +2025-12-22 12:12:14 - 数据库插入成功 +2025-12-22 12:12:14 - 上传线程结束: /mnt/save/warning/alarm_20251220_113455.jpg: 成功! +2025-12-22 12:12:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:14 +2025-12-22 12:12:14 - 数据库插入成功 +2025-12-22 12:12:15 - 上传线程结束: /mnt/save/warning/alarm_20251220_113302.jpg: 成功! +2025-12-22 12:12:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:15 +2025-12-22 12:12:15 - 数据库插入成功 +2025-12-22 12:12:15 - 上传线程结束: /mnt/save/warning/alarm_20251220_113017.jpg: 成功! +2025-12-22 12:12:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:16 +2025-12-22 12:12:16 - 数据库插入成功 +2025-12-22 12:12:16 - 上传线程结束: /mnt/save/warning/alarm_20251220_113035.jpg: 成功! +2025-12-22 12:12:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:17 +2025-12-22 12:12:18 - 数据库插入成功 +2025-12-22 12:12:18 - 上传线程结束: /mnt/save/warning/alarm_20251222_121116.jpg: 成功! +2025-12-22 12:12:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:18 +2025-12-22 12:12:19 - 数据库插入成功 +2025-12-22 12:12:19 - 上传线程结束: /mnt/save/warning/alarm_20251222_121146.jpg: 成功! +2025-12-22 12:12:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:12:19 +2025-12-22 12:12:19 - 数据库插入成功 +2025-12-22 12:12:19 - 上传线程结束: /mnt/save/warning/alarm_20251222_121148.jpg: 成功! +2025-12-22 12:12:31 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 12:13:08 - 读取ICCID成功: 898604581824D0366321 +2025-12-22 12:13:26 - 开始上传图片: /mnt/save/warning/alarm_20251222_121324.jpg +2025-12-22 12:13:29 - 上传图片 /mnt/save/warning/alarm_20251222_121324.jpg 时出错: [Errno 113] No route to host +2025-12-22 12:13:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:14:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:14:10 - 开始上传图片: /mnt/save/warning/alarm_20251222_121408.jpg +2025-12-22 12:14:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:14:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:14:10 +2025-12-22 12:14:11 - 数据库插入成功 +2025-12-22 12:14:11 - 上传线程结束: /mnt/save/warning/alarm_20251222_121408.jpg: 成功! +2025-12-22 12:14:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:15:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:15:12 - 开始上传图片: /mnt/save/warning/alarm_20251222_121512.jpg +2025-12-22 12:15:13 - 上传图片 /mnt/save/warning/alarm_20251222_121512.jpg 时出错: [Errno 113] No route to host +2025-12-22 12:15:36 - 开始上传图片: /mnt/save/warning/alarm_20251222_121535.jpg +2025-12-22 12:15:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:15:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:15:36 +2025-12-22 12:15:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:15:46 - 数据库插入失败: (2003, "Can't connect to MySQL server on '116.147.36.110' (timed out)") +2025-12-22 12:15:46 - 上传线程结束: /mnt/save/warning/alarm_20251222_121535.jpg: 成功! +2025-12-22 12:16:08 - 开始上传图片: /mnt/save/warning/alarm_20251222_121608.jpg +2025-12-22 12:16:09 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:16:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:16:09 +2025-12-22 12:16:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:16:10 - 数据库插入成功 +2025-12-22 12:16:10 - 上传线程结束: /mnt/save/warning/alarm_20251222_121608.jpg: 成功! +2025-12-22 12:16:17 - 开始上传图片: /mnt/save/warning/alarm_20251222_121616.jpg +2025-12-22 12:16:20 - 上传图片 /mnt/save/warning/alarm_20251222_121616.jpg 时出错: [Errno 113] No route to host +2025-12-22 12:16:21 - 开始上传图片: /mnt/save/warning/alarm_20251222_121620.jpg +2025-12-22 12:16:24 - 上传图片 /mnt/save/warning/alarm_20251222_121620.jpg 时出错: [Errno 113] No route to host +2025-12-22 12:16:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:16:43 - 开始上传图片: /mnt/save/warning/alarm_20251222_121642.jpg +2025-12-22 12:16:43 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:16:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:16:43 +2025-12-22 12:16:44 - 数据库插入成功 +2025-12-22 12:16:44 - 上传线程结束: /mnt/save/warning/alarm_20251222_121642.jpg: 成功! +2025-12-22 12:16:49 - 开始上传图片: /mnt/save/warning/alarm_20251222_121647.jpg +2025-12-22 12:16:49 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:16:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:16:49 +2025-12-22 12:16:50 - 数据库插入成功 +2025-12-22 12:16:50 - 上传线程结束: /mnt/save/warning/alarm_20251222_121647.jpg: 成功! +2025-12-22 12:17:03 - 开始上传图片: /mnt/save/warning/alarm_20251222_121702.jpg +2025-12-22 12:17:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:17:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:17:03 +2025-12-22 12:17:04 - 数据库插入成功 +2025-12-22 12:17:04 - 上传线程结束: /mnt/save/warning/alarm_20251222_121702.jpg: 成功! +2025-12-22 12:17:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:17:17 - 开始上传图片: /mnt/save/warning/alarm_20251222_121716.jpg +2025-12-22 12:17:17 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:17:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:17:17 +2025-12-22 12:17:18 - 数据库插入成功 +2025-12-22 12:17:18 - 上传线程结束: /mnt/save/warning/alarm_20251222_121716.jpg: 成功! +2025-12-22 12:17:27 - 开始上传图片: /mnt/save/warning/alarm_20251222_121726.jpg +2025-12-22 12:17:27 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:17:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:17:27 +2025-12-22 12:17:28 - 数据库插入成功 +2025-12-22 12:17:28 - 上传线程结束: /mnt/save/warning/alarm_20251222_121726.jpg: 成功! +2025-12-22 12:17:35 - 开始上传图片: /mnt/save/warning/alarm_20251222_121734.jpg +2025-12-22 12:17:35 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:17:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:17:35 +2025-12-22 12:17:36 - 数据库插入成功 +2025-12-22 12:17:36 - 上传线程结束: /mnt/save/warning/alarm_20251222_121734.jpg: 成功! +2025-12-22 12:17:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:17:39 - 开始上传图片: /mnt/save/warning/alarm_20251222_121738.jpg +2025-12-22 12:17:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:17:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:17:39 +2025-12-22 12:17:40 - 数据库插入成功 +2025-12-22 12:17:40 - 上传线程结束: /mnt/save/warning/alarm_20251222_121738.jpg: 成功! +2025-12-22 12:17:41 - 开始上传图片: /mnt/save/warning/alarm_20251222_121740.jpg +2025-12-22 12:17:41 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:17:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:17:41 +2025-12-22 12:17:42 - 数据库插入成功 +2025-12-22 12:17:42 - 上传线程结束: /mnt/save/warning/alarm_20251222_121740.jpg: 成功! +2025-12-22 12:17:45 - 开始上传图片: /mnt/save/warning/alarm_20251222_121745.jpg +2025-12-22 12:17:45 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:17:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:17:45 +2025-12-22 12:17:46 - 数据库插入成功 +2025-12-22 12:17:46 - 上传线程结束: /mnt/save/warning/alarm_20251222_121745.jpg: 成功! +2025-12-22 12:17:49 - 开始上传图片: /mnt/save/warning/alarm_20251222_121748.jpg +2025-12-22 12:17:49 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:17:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:17:49 +2025-12-22 12:17:50 - 数据库插入成功 +2025-12-22 12:17:50 - 上传线程结束: /mnt/save/warning/alarm_20251222_121748.jpg: 成功! +2025-12-22 12:17:59 - 开始上传图片: /mnt/save/warning/alarm_20251222_121759.jpg +2025-12-22 12:17:59 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:17:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:17:59 +2025-12-22 12:18:00 - 数据库插入成功 +2025-12-22 12:18:00 - 上传线程结束: /mnt/save/warning/alarm_20251222_121759.jpg: 成功! +2025-12-22 12:18:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:18:15 - 开始上传图片: /mnt/save/warning/alarm_20251222_121814.jpg +2025-12-22 12:18:15 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:18:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:18:15 +2025-12-22 12:18:16 - 数据库插入成功 +2025-12-22 12:18:16 - 上传线程结束: /mnt/save/warning/alarm_20251222_121814.jpg: 成功! +2025-12-22 12:18:19 - 开始上传图片: /mnt/save/warning/alarm_20251222_121819.jpg +2025-12-22 12:18:19 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:18:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:18:19 +2025-12-22 12:18:21 - 数据库插入成功 +2025-12-22 12:18:21 - 上传线程结束: /mnt/save/warning/alarm_20251222_121819.jpg: 成功! +2025-12-22 12:18:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:19:03 - 开始上传图片: /mnt/save/warning/alarm_20251222_121902.jpg +2025-12-22 12:19:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:19:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:19:04 +2025-12-22 12:19:05 - 数据库插入成功 +2025-12-22 12:19:05 - 上传线程结束: /mnt/save/warning/alarm_20251222_121902.jpg: 成功! +2025-12-22 12:19:07 - 开始上传图片: /mnt/save/warning/alarm_20251222_121907.jpg +2025-12-22 12:19:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:19:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:19:07 +2025-12-22 12:19:08 - 数据库插入成功 +2025-12-22 12:19:08 - 上传线程结束: /mnt/save/warning/alarm_20251222_121907.jpg: 成功! +2025-12-22 12:19:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:19:17 - 开始上传图片: /mnt/save/warning/alarm_20251222_121917.jpg +2025-12-22 12:19:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:19:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:19:18 +2025-12-22 12:19:18 - 数据库插入成功 +2025-12-22 12:19:18 - 上传线程结束: /mnt/save/warning/alarm_20251222_121917.jpg: 成功! +2025-12-22 12:19:22 - 开始上传图片: /mnt/save/warning/alarm_20251222_121920.jpg +2025-12-22 12:19:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:19:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:19:22 +2025-12-22 12:19:24 - 开始上传图片: /mnt/save/warning/alarm_20251222_121923.jpg +2025-12-22 12:19:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:19:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:19:24 +2025-12-22 12:19:24 - 数据库插入成功 +2025-12-22 12:19:24 - 上传线程结束: /mnt/save/warning/alarm_20251222_121920.jpg: 成功! +2025-12-22 12:19:24 - 数据库插入成功 +2025-12-22 12:19:24 - 上传线程结束: /mnt/save/warning/alarm_20251222_121923.jpg: 成功! +2025-12-22 12:19:28 - 开始上传图片: /mnt/save/warning/alarm_20251222_121927.jpg +2025-12-22 12:19:28 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:19:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:19:28 +2025-12-22 12:19:28 - 数据库插入成功 +2025-12-22 12:19:28 - 上传线程结束: /mnt/save/warning/alarm_20251222_121927.jpg: 成功! +2025-12-22 12:19:36 - 开始上传图片: /mnt/save/warning/alarm_20251222_121936.jpg +2025-12-22 12:19:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:19:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:19:36 +2025-12-22 12:19:37 - 数据库插入成功 +2025-12-22 12:19:37 - 上传线程结束: /mnt/save/warning/alarm_20251222_121936.jpg: 成功! +2025-12-22 12:19:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:19:50 - 开始上传图片: /mnt/save/warning/alarm_20251222_121948.jpg +2025-12-22 12:19:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:19:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:19:50 +2025-12-22 12:19:51 - 数据库插入成功 +2025-12-22 12:19:51 - 上传线程结束: /mnt/save/warning/alarm_20251222_121948.jpg: 成功! +2025-12-22 12:20:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:20:18 - 开始上传图片: /mnt/save/warning/alarm_20251222_122017.jpg +2025-12-22 12:20:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:20:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:20:18 +2025-12-22 12:20:20 - 数据库插入成功 +2025-12-22 12:20:20 - 上传线程结束: /mnt/save/warning/alarm_20251222_122017.jpg: 成功! +2025-12-22 12:20:34 - 开始上传图片: /mnt/save/warning/alarm_20251222_122033.jpg +2025-12-22 12:20:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:20:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:20:34 +2025-12-22 12:20:35 - 数据库插入成功 +2025-12-22 12:20:35 - 上传线程结束: /mnt/save/warning/alarm_20251222_122033.jpg: 成功! +2025-12-22 12:20:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:20:42 - 开始上传图片: /mnt/save/warning/alarm_20251222_122041.jpg +2025-12-22 12:20:42 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:20:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:20:42 +2025-12-22 12:20:43 - 数据库插入成功 +2025-12-22 12:20:43 - 上传线程结束: /mnt/save/warning/alarm_20251222_122041.jpg: 成功! +2025-12-22 12:21:02 - 开始上传图片: /mnt/save/warning/alarm_20251222_122102.jpg +2025-12-22 12:21:02 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:21:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:21:02 +2025-12-22 12:21:03 - 数据库插入成功 +2025-12-22 12:21:03 - 上传线程结束: /mnt/save/warning/alarm_20251222_122102.jpg: 成功! +2025-12-22 12:21:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:21:16 - 开始上传图片: /mnt/save/warning/alarm_20251222_122116.jpg +2025-12-22 12:21:16 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:21:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:21:16 +2025-12-22 12:21:17 - 数据库插入成功 +2025-12-22 12:21:17 - 上传线程结束: /mnt/save/warning/alarm_20251222_122116.jpg: 成功! +2025-12-22 12:21:18 - 开始上传图片: /mnt/save/warning/alarm_20251222_122118.jpg +2025-12-22 12:21:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:21:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:21:18 +2025-12-22 12:21:20 - 数据库插入成功 +2025-12-22 12:21:20 - 上传线程结束: /mnt/save/warning/alarm_20251222_122118.jpg: 成功! +2025-12-22 12:21:34 - 开始上传图片: /mnt/save/warning/alarm_20251222_122133.jpg +2025-12-22 12:21:35 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:21:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:21:35 +2025-12-22 12:21:35 - 数据库插入成功 +2025-12-22 12:21:35 - 上传线程结束: /mnt/save/warning/alarm_20251222_122133.jpg: 成功! +2025-12-22 12:21:38 - 开始上传图片: /mnt/save/warning/alarm_20251222_122138.jpg +2025-12-22 12:21:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:21:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:21:39 +2025-12-22 12:21:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:21:39 - 数据库插入成功 +2025-12-22 12:21:39 - 上传线程结束: /mnt/save/warning/alarm_20251222_122138.jpg: 成功! +2025-12-22 12:21:40 - 开始上传图片: /mnt/save/warning/alarm_20251222_122140.jpg +2025-12-22 12:21:41 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:21:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:21:41 +2025-12-22 12:21:41 - 数据库插入成功 +2025-12-22 12:21:41 - 上传线程结束: /mnt/save/warning/alarm_20251222_122140.jpg: 成功! +2025-12-22 12:21:51 - 开始上传图片: /mnt/save/warning/alarm_20251222_122150.jpg +2025-12-22 12:21:51 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:21:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:21:51 +2025-12-22 12:21:51 - 数据库插入成功 +2025-12-22 12:21:51 - 上传线程结束: /mnt/save/warning/alarm_20251222_122150.jpg: 成功! +2025-12-22 12:22:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:22:11 - 开始上传图片: /mnt/save/warning/alarm_20251222_122209.jpg +2025-12-22 12:22:11 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:22:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:22:11 +2025-12-22 12:22:11 - 数据库插入成功 +2025-12-22 12:22:11 - 上传线程结束: /mnt/save/warning/alarm_20251222_122209.jpg: 成功! +2025-12-22 12:22:27 - 开始上传图片: /mnt/save/warning/alarm_20251222_122225.jpg +2025-12-22 12:22:27 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:22:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:22:27 +2025-12-22 12:22:28 - 数据库插入成功 +2025-12-22 12:22:28 - 上传线程结束: /mnt/save/warning/alarm_20251222_122225.jpg: 成功! +2025-12-22 12:22:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:23:05 - 开始上传图片: /mnt/save/warning/alarm_20251222_122304.jpg +2025-12-22 12:23:05 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:23:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:23:05 +2025-12-22 12:23:06 - 数据库插入成功 +2025-12-22 12:23:06 - 上传线程结束: /mnt/save/warning/alarm_20251222_122304.jpg: 成功! +2025-12-22 12:23:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:23:17 - 开始上传图片: /mnt/save/warning/alarm_20251222_122315.jpg +2025-12-22 12:23:17 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:23:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:23:17 +2025-12-22 12:23:18 - 数据库插入成功 +2025-12-22 12:23:18 - 上传线程结束: /mnt/save/warning/alarm_20251222_122315.jpg: 成功! +2025-12-22 12:23:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:23:41 - 开始上传图片: /mnt/save/warning/alarm_20251222_122341.jpg +2025-12-22 12:23:41 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:23:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:23:41 +2025-12-22 12:23:42 - 数据库插入成功 +2025-12-22 12:23:42 - 上传线程结束: /mnt/save/warning/alarm_20251222_122341.jpg: 成功! +2025-12-22 12:23:55 - 开始上传图片: /mnt/save/warning/alarm_20251222_122354.jpg +2025-12-22 12:23:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:23:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:23:55 +2025-12-22 12:23:56 - 数据库插入成功 +2025-12-22 12:23:56 - 上传线程结束: /mnt/save/warning/alarm_20251222_122354.jpg: 成功! +2025-12-22 12:24:01 - 开始上传图片: /mnt/save/warning/alarm_20251222_122400.jpg +2025-12-22 12:24:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:24:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:24:01 +2025-12-22 12:24:02 - 数据库插入成功 +2025-12-22 12:24:02 - 上传线程结束: /mnt/save/warning/alarm_20251222_122400.jpg: 成功! +2025-12-22 12:24:09 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:24:30 - 开始上传图片: /mnt/save/warning/alarm_20251222_122428.jpg +2025-12-22 12:24:30 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:24:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:24:30 +2025-12-22 12:24:31 - 数据库插入成功 +2025-12-22 12:24:31 - 上传线程结束: /mnt/save/warning/alarm_20251222_122428.jpg: 成功! +2025-12-22 12:24:34 - 开始上传图片: /mnt/save/warning/alarm_20251222_122432.jpg +2025-12-22 12:24:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:24:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:24:34 +2025-12-22 12:24:36 - 数据库插入成功 +2025-12-22 12:24:36 - 上传线程结束: /mnt/save/warning/alarm_20251222_122432.jpg: 成功! +2025-12-22 12:24:39 - 读取ICCID异常: (5, 'Input/output error') +2025-12-22 12:24:48 - 开始上传图片: /mnt/save/warning/alarm_20251222_122447.jpg +2025-12-22 12:24:50 - 上传图片 /mnt/save/warning/alarm_20251222_122447.jpg 时出错: [Errno 113] No route to host +2025-12-22 12:24:58 - 开始上传图片: /mnt/save/warning/alarm_20251222_122456.jpg +2025-12-22 12:24:58 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2025-12-22 12:24:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-22 12:24:58 +2025-12-22 12:24:58 - 数据库插入成功 +2025-12-22 12:24:58 - 上传线程结束: /mnt/save/warning/alarm_20251222_122456.jpg: 成功! +2025-12-24 11:45:55 - ICCID刷新线程启动 +2025-12-24 11:46:02 - 读取ICCID成功: 898604581824D0366321 +2025-12-24 11:46:18 - 开始上传图片: /mnt/save/warning/alarm_20251224_114617.jpg +2025-12-24 11:46:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2025-12-24 11:46:19 +2025-12-24 11:46:20 - 数据库插入成功 +2025-12-24 11:46:20 - 上传线程结束: /mnt/save/warning/alarm_20251224_114617.jpg: 成功! +2025-12-24 11:46:39 - 读取ICCID成功: 898604581824D0366321 +2026-01-04 14:17:11 - ICCID刷新线程启动 +2026-01-09 12:03:20 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:03:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_120328.jpg +2026-01-09 12:03:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:03:30 +2026-01-09 12:03:31 - 数据库插入成功 +2026-01-09 12:03:31 - 上传线程结束: /mnt/save/warning/alarm_20260109_120328.jpg: 成功! +2026-01-09 12:03:35 - 开始上传图片: /mnt/save/warning/alarm_20260109_120333.jpg +2026-01-09 12:03:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:03:36 +2026-01-09 12:03:37 - 开始上传图片: /mnt/save/warning/alarm_20260109_120336.jpg +2026-01-09 12:03:37 - 数据库插入成功 +2026-01-09 12:03:37 - 上传线程结束: /mnt/save/warning/alarm_20260109_120333.jpg: 成功! +2026-01-09 12:03:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:03:38 +2026-01-09 12:03:39 - 数据库插入成功 +2026-01-09 12:03:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_120336.jpg: 成功! +2026-01-09 12:03:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_120338.jpg +2026-01-09 12:03:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:03:40 +2026-01-09 12:03:41 - 数据库插入成功 +2026-01-09 12:03:41 - 上传线程结束: /mnt/save/warning/alarm_20260109_120338.jpg: 成功! +2026-01-09 12:03:43 - 开始上传图片: /mnt/save/warning/alarm_20260109_120341.jpg +2026-01-09 12:03:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:03:44 +2026-01-09 12:03:45 - 数据库插入成功 +2026-01-09 12:03:45 - 上传线程结束: /mnt/save/warning/alarm_20260109_120341.jpg: 成功! +2026-01-09 12:03:47 - 开始上传图片: /mnt/save/warning/alarm_20260109_120346.jpg +2026-01-09 12:03:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:03:48 +2026-01-09 12:03:49 - 数据库插入成功 +2026-01-09 12:03:49 - 上传线程结束: /mnt/save/warning/alarm_20260109_120346.jpg: 成功! +2026-01-09 12:03:51 - 开始上传图片: /mnt/save/warning/alarm_20260109_120349.jpg +2026-01-09 12:03:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:03:52 +2026-01-09 12:03:53 - 数据库插入成功 +2026-01-09 12:03:53 - 上传线程结束: /mnt/save/warning/alarm_20260109_120349.jpg: 成功! +2026-01-09 12:03:53 - 开始上传图片: /mnt/save/warning/alarm_20260109_120353.jpg +2026-01-09 12:03:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:03:54 +2026-01-09 12:03:55 - 数据库插入成功 +2026-01-09 12:03:55 - 上传线程结束: /mnt/save/warning/alarm_20260109_120353.jpg: 成功! +2026-01-09 12:03:57 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:04:01 - 开始上传图片: /mnt/save/warning/alarm_20260109_120400.jpg +2026-01-09 12:04:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:04:02 +2026-01-09 12:04:03 - 数据库插入成功 +2026-01-09 12:04:03 - 上传线程结束: /mnt/save/warning/alarm_20260109_120400.jpg: 成功! +2026-01-09 12:04:31 - 开始上传图片: /mnt/save/warning/alarm_20260109_120431.jpg +2026-01-09 12:04:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:04:32 +2026-01-09 12:04:33 - 数据库插入成功 +2026-01-09 12:04:33 - 上传线程结束: /mnt/save/warning/alarm_20260109_120431.jpg: 成功! +2026-01-09 12:04:34 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:04:41 - 开始上传图片: /mnt/save/warning/alarm_20260109_120441.jpg +2026-01-09 12:04:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:04:42 +2026-01-09 12:04:43 - 数据库插入成功 +2026-01-09 12:04:43 - 上传线程结束: /mnt/save/warning/alarm_20260109_120441.jpg: 成功! +2026-01-09 12:04:51 - 开始上传图片: /mnt/save/warning/alarm_20260109_120451.jpg +2026-01-09 12:04:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:04:53 +2026-01-09 12:04:53 - 数据库插入成功 +2026-01-09 12:04:53 - 上传线程结束: /mnt/save/warning/alarm_20260109_120451.jpg: 成功! +2026-01-09 12:04:55 - 开始上传图片: /mnt/save/warning/alarm_20260109_120455.jpg +2026-01-09 12:04:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:04:57 +2026-01-09 12:04:57 - 数据库插入成功 +2026-01-09 12:04:57 - 上传线程结束: /mnt/save/warning/alarm_20260109_120455.jpg: 成功! +2026-01-09 12:05:08 - 开始上传图片: /mnt/save/warning/alarm_20260109_120507.jpg +2026-01-09 12:05:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:05:09 +2026-01-09 12:05:10 - 数据库插入成功 +2026-01-09 12:05:10 - 上传线程结束: /mnt/save/warning/alarm_20260109_120507.jpg: 成功! +2026-01-09 12:05:11 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:05:12 - 开始上传图片: /mnt/save/warning/alarm_20260109_120510.jpg +2026-01-09 12:05:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:05:13 +2026-01-09 12:05:13 - 数据库插入成功 +2026-01-09 12:05:13 - 上传线程结束: /mnt/save/warning/alarm_20260109_120510.jpg: 成功! +2026-01-09 12:05:20 - 开始上传图片: /mnt/save/warning/alarm_20260109_120519.jpg +2026-01-09 12:05:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:05:21 +2026-01-09 12:05:21 - 数据库插入成功 +2026-01-09 12:05:21 - 上传线程结束: /mnt/save/warning/alarm_20260109_120519.jpg: 成功! +2026-01-09 12:05:36 - 开始上传图片: /mnt/save/warning/alarm_20260109_120534.jpg +2026-01-09 12:05:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:05:37 +2026-01-09 12:05:38 - 数据库插入成功 +2026-01-09 12:05:38 - 上传线程结束: /mnt/save/warning/alarm_20260109_120534.jpg: 成功! +2026-01-09 12:05:48 - 开始上传图片: /mnt/save/warning/alarm_20260109_120546.jpg +2026-01-09 12:05:48 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:05:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:05:49 +2026-01-09 12:05:49 - 数据库插入成功 +2026-01-09 12:05:49 - 上传线程结束: /mnt/save/warning/alarm_20260109_120546.jpg: 成功! +2026-01-09 12:05:54 - 开始上传图片: /mnt/save/warning/alarm_20260109_120552.jpg +2026-01-09 12:05:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:05:55 +2026-01-09 12:05:56 - 数据库插入成功 +2026-01-09 12:05:56 - 上传线程结束: /mnt/save/warning/alarm_20260109_120552.jpg: 成功! +2026-01-09 12:06:00 - 开始上传图片: /mnt/save/warning/alarm_20260109_120600.jpg +2026-01-09 12:06:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:06:01 +2026-01-09 12:06:02 - 数据库插入成功 +2026-01-09 12:06:02 - 上传线程结束: /mnt/save/warning/alarm_20260109_120600.jpg: 成功! +2026-01-09 12:06:16 - 开始上传图片: /mnt/save/warning/alarm_20260109_120616.jpg +2026-01-09 12:06:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:06:17 +2026-01-09 12:06:18 - 数据库插入成功 +2026-01-09 12:06:18 - 上传线程结束: /mnt/save/warning/alarm_20260109_120616.jpg: 成功! +2026-01-09 12:06:22 - 读取ICCID异常: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:06:36 - 开始上传图片: /mnt/save/warning/alarm_20260109_120636.jpg +2026-01-09 12:06:39 - 上传图片 /mnt/save/warning/alarm_20260109_120636.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:06:50 - 开始上传图片: /mnt/save/warning/alarm_20260109_120648.jpg +2026-01-09 12:06:52 - 上传图片 /mnt/save/warning/alarm_20260109_120648.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:06:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:07:02 - 开始上传图片: /mnt/save/warning/alarm_20260109_120702.jpg +2026-01-09 12:07:05 - 上传图片 /mnt/save/warning/alarm_20260109_120702.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:07:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:07:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_120727.jpg +2026-01-09 12:07:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:07:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:07:29 +2026-01-09 12:07:29 - 数据库插入成功 +2026-01-09 12:07:29 - 上传线程结束: /mnt/save/warning/alarm_20260109_120727.jpg: 成功! +2026-01-09 12:07:51 - 开始上传图片: /mnt/save/warning/alarm_20260109_120750.jpg +2026-01-09 12:07:51 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:07:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:07:51 +2026-01-09 12:07:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:07:52 - 数据库插入成功 +2026-01-09 12:07:52 - 上传线程结束: /mnt/save/warning/alarm_20260109_120750.jpg: 成功! +2026-01-09 12:08:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_120808.jpg +2026-01-09 12:08:09 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:08:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:08:09 +2026-01-09 12:08:10 - 数据库插入成功 +2026-01-09 12:08:10 - 上传线程结束: /mnt/save/warning/alarm_20260109_120808.jpg: 成功! +2026-01-09 12:08:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:08:23 - 开始上传图片: /mnt/save/warning/alarm_20260109_120822.jpg +2026-01-09 12:08:23 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:08:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:08:23 +2026-01-09 12:08:24 - 数据库插入成功 +2026-01-09 12:08:24 - 上传线程结束: /mnt/save/warning/alarm_20260109_120822.jpg: 成功! +2026-01-09 12:08:33 - 开始上传图片: /mnt/save/warning/alarm_20260109_120831.jpg +2026-01-09 12:08:33 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:08:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:08:33 +2026-01-09 12:08:34 - 数据库插入成功 +2026-01-09 12:08:34 - 上传线程结束: /mnt/save/warning/alarm_20260109_120831.jpg: 成功! +2026-01-09 12:08:51 - 开始上传图片: /mnt/save/warning/alarm_20260109_120850.jpg +2026-01-09 12:08:51 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:08:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:08:51 +2026-01-09 12:08:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:08:55 - 数据库插入成功 +2026-01-09 12:08:55 - 上传线程结束: /mnt/save/warning/alarm_20260109_120850.jpg: 成功! +2026-01-09 12:09:01 - 开始上传图片: /mnt/save/warning/alarm_20260109_120901.jpg +2026-01-09 12:09:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:09:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:09:01 +2026-01-09 12:09:02 - 数据库插入成功 +2026-01-09 12:09:02 - 上传线程结束: /mnt/save/warning/alarm_20260109_120901.jpg: 成功! +2026-01-09 12:09:11 - 开始上传图片: /mnt/save/warning/alarm_20260109_120911.jpg +2026-01-09 12:09:11 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:09:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:09:11 +2026-01-09 12:09:12 - 数据库插入成功 +2026-01-09 12:09:12 - 上传线程结束: /mnt/save/warning/alarm_20260109_120911.jpg: 成功! +2026-01-09 12:09:17 - 开始上传图片: /mnt/save/warning/alarm_20260109_120917.jpg +2026-01-09 12:09:17 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:09:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:09:17 +2026-01-09 12:09:18 - 数据库插入成功 +2026-01-09 12:09:18 - 上传线程结束: /mnt/save/warning/alarm_20260109_120917.jpg: 成功! +2026-01-09 12:09:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:09:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:09:54 - 开始上传图片: /mnt/save/warning/alarm_20260109_120952.jpg +2026-01-09 12:09:54 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:09:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:09:54 +2026-01-09 12:09:55 - 数据库插入成功 +2026-01-09 12:09:55 - 上传线程结束: /mnt/save/warning/alarm_20260109_120952.jpg: 成功! +2026-01-09 12:10:06 - 开始上传图片: /mnt/save/warning/alarm_20260109_121006.jpg +2026-01-09 12:10:06 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:10:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:10:06 +2026-01-09 12:10:07 - 数据库插入成功 +2026-01-09 12:10:07 - 上传线程结束: /mnt/save/warning/alarm_20260109_121006.jpg: 成功! +2026-01-09 12:10:20 - 开始上传图片: /mnt/save/warning/alarm_20260109_121018.jpg +2026-01-09 12:10:20 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:10:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:10:20 +2026-01-09 12:10:21 - 数据库插入成功 +2026-01-09 12:10:21 - 上传线程结束: /mnt/save/warning/alarm_20260109_121018.jpg: 成功! +2026-01-09 12:10:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:10:22 - 开始上传图片: /mnt/save/warning/alarm_20260109_121020.jpg +2026-01-09 12:10:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:10:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:10:22 +2026-01-09 12:10:23 - 数据库插入成功 +2026-01-09 12:10:23 - 上传线程结束: /mnt/save/warning/alarm_20260109_121020.jpg: 成功! +2026-01-09 12:10:28 - 开始上传图片: /mnt/save/warning/alarm_20260109_121026.jpg +2026-01-09 12:10:28 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:10:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:10:28 +2026-01-09 12:10:29 - 数据库插入成功 +2026-01-09 12:10:29 - 上传线程结束: /mnt/save/warning/alarm_20260109_121026.jpg: 成功! +2026-01-09 12:10:30 - 开始上传图片: /mnt/save/warning/alarm_20260109_121029.jpg +2026-01-09 12:10:30 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:10:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:10:30 +2026-01-09 12:10:31 - 数据库插入成功 +2026-01-09 12:10:31 - 上传线程结束: /mnt/save/warning/alarm_20260109_121029.jpg: 成功! +2026-01-09 12:10:42 - 开始上传图片: /mnt/save/warning/alarm_20260109_121041.jpg +2026-01-09 12:10:42 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:10:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:10:42 +2026-01-09 12:10:43 - 数据库插入成功 +2026-01-09 12:10:43 - 上传线程结束: /mnt/save/warning/alarm_20260109_121041.jpg: 成功! +2026-01-09 12:10:50 - 开始上传图片: /mnt/save/warning/alarm_20260109_121050.jpg +2026-01-09 12:10:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:10:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:10:50 +2026-01-09 12:10:51 - 数据库插入成功 +2026-01-09 12:10:51 - 上传线程结束: /mnt/save/warning/alarm_20260109_121050.jpg: 成功! +2026-01-09 12:10:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:11:00 - 开始上传图片: /mnt/save/warning/alarm_20260109_121059.jpg +2026-01-09 12:11:00 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:11:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:11:00 +2026-01-09 12:11:01 - 数据库插入成功 +2026-01-09 12:11:01 - 上传线程结束: /mnt/save/warning/alarm_20260109_121059.jpg: 成功! +2026-01-09 12:11:14 - 开始上传图片: /mnt/save/warning/alarm_20260109_121112.jpg +2026-01-09 12:11:15 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:11:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:11:15 +2026-01-09 12:11:15 - 数据库插入成功 +2026-01-09 12:11:15 - 上传线程结束: /mnt/save/warning/alarm_20260109_121112.jpg: 成功! +2026-01-09 12:11:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:11:30 - 开始上传图片: /mnt/save/warning/alarm_20260109_121130.jpg +2026-01-09 12:11:31 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:11:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:11:31 +2026-01-09 12:11:32 - 数据库插入成功 +2026-01-09 12:11:32 - 上传线程结束: /mnt/save/warning/alarm_20260109_121130.jpg: 成功! +2026-01-09 12:11:38 - 开始上传图片: /mnt/save/warning/alarm_20260109_121137.jpg +2026-01-09 12:11:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:11:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:11:39 +2026-01-09 12:11:40 - 数据库插入成功 +2026-01-09 12:11:40 - 上传线程结束: /mnt/save/warning/alarm_20260109_121137.jpg: 成功! +2026-01-09 12:11:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:12:01 - 开始上传图片: /mnt/save/warning/alarm_20260109_121159.jpg +2026-01-09 12:12:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:12:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:12:01 +2026-01-09 12:12:02 - 数据库插入成功 +2026-01-09 12:12:02 - 上传线程结束: /mnt/save/warning/alarm_20260109_121159.jpg: 成功! +2026-01-09 12:12:07 - 开始上传图片: /mnt/save/warning/alarm_20260109_121207.jpg +2026-01-09 12:12:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:12:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:12:07 +2026-01-09 12:12:08 - 数据库插入成功 +2026-01-09 12:12:08 - 上传线程结束: /mnt/save/warning/alarm_20260109_121207.jpg: 成功! +2026-01-09 12:12:13 - 开始上传图片: /mnt/save/warning/alarm_20260109_121212.jpg +2026-01-09 12:12:13 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:12:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:12:13 +2026-01-09 12:12:14 - 数据库插入成功 +2026-01-09 12:12:14 - 上传线程结束: /mnt/save/warning/alarm_20260109_121212.jpg: 成功! +2026-01-09 12:12:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:12:23 - 开始上传图片: /mnt/save/warning/alarm_20260109_121221.jpg +2026-01-09 12:12:23 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:12:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:12:23 +2026-01-09 12:12:24 - 数据库插入成功 +2026-01-09 12:12:24 - 上传线程结束: /mnt/save/warning/alarm_20260109_121221.jpg: 成功! +2026-01-09 12:12:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_121229.jpg +2026-01-09 12:12:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:12:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:12:29 +2026-01-09 12:12:30 - 数据库插入成功 +2026-01-09 12:12:30 - 上传线程结束: /mnt/save/warning/alarm_20260109_121229.jpg: 成功! +2026-01-09 12:12:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_121238.jpg +2026-01-09 12:12:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:12:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:12:39 +2026-01-09 12:12:40 - 数据库插入成功 +2026-01-09 12:12:40 - 上传线程结束: /mnt/save/warning/alarm_20260109_121238.jpg: 成功! +2026-01-09 12:12:47 - 开始上传图片: /mnt/save/warning/alarm_20260109_121245.jpg +2026-01-09 12:12:47 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:12:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:12:47 +2026-01-09 12:12:48 - 数据库插入成功 +2026-01-09 12:12:48 - 上传线程结束: /mnt/save/warning/alarm_20260109_121245.jpg: 成功! +2026-01-09 12:12:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:12:55 - 开始上传图片: /mnt/save/warning/alarm_20260109_121255.jpg +2026-01-09 12:12:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:12:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:12:55 +2026-01-09 12:12:56 - 数据库插入成功 +2026-01-09 12:12:56 - 上传线程结束: /mnt/save/warning/alarm_20260109_121255.jpg: 成功! +2026-01-09 12:13:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:13:27 - 开始上传图片: /mnt/save/warning/alarm_20260109_121326.jpg +2026-01-09 12:13:27 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:13:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:13:27 +2026-01-09 12:13:28 - 数据库插入成功 +2026-01-09 12:13:28 - 上传线程结束: /mnt/save/warning/alarm_20260109_121326.jpg: 成功! +2026-01-09 12:13:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_121329.jpg +2026-01-09 12:13:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:13:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:13:29 +2026-01-09 12:13:32 - 数据库插入成功 +2026-01-09 12:13:32 - 上传线程结束: /mnt/save/warning/alarm_20260109_121329.jpg: 成功! +2026-01-09 12:13:33 - 开始上传图片: /mnt/save/warning/alarm_20260109_121331.jpg +2026-01-09 12:13:33 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:13:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:13:33 +2026-01-09 12:13:34 - 数据库插入成功 +2026-01-09 12:13:34 - 上传线程结束: /mnt/save/warning/alarm_20260109_121331.jpg: 成功! +2026-01-09 12:13:35 - 开始上传图片: /mnt/save/warning/alarm_20260109_121335.jpg +2026-01-09 12:13:35 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:13:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:13:35 +2026-01-09 12:13:36 - 数据库插入成功 +2026-01-09 12:13:36 - 上传线程结束: /mnt/save/warning/alarm_20260109_121335.jpg: 成功! +2026-01-09 12:13:37 - 开始上传图片: /mnt/save/warning/alarm_20260109_121337.jpg +2026-01-09 12:13:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:13:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:13:37 +2026-01-09 12:13:38 - 数据库插入成功 +2026-01-09 12:13:38 - 上传线程结束: /mnt/save/warning/alarm_20260109_121337.jpg: 成功! +2026-01-09 12:13:41 - 开始上传图片: /mnt/save/warning/alarm_20260109_121340.jpg +2026-01-09 12:13:41 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:13:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:13:41 +2026-01-09 12:13:42 - 数据库插入成功 +2026-01-09 12:13:42 - 上传线程结束: /mnt/save/warning/alarm_20260109_121340.jpg: 成功! +2026-01-09 12:13:49 - 开始上传图片: /mnt/save/warning/alarm_20260109_121348.jpg +2026-01-09 12:13:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:13:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:13:50 +2026-01-09 12:13:51 - 数据库插入成功 +2026-01-09 12:13:51 - 上传线程结束: /mnt/save/warning/alarm_20260109_121348.jpg: 成功! +2026-01-09 12:13:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:13:59 - 开始上传图片: /mnt/save/warning/alarm_20260109_121359.jpg +2026-01-09 12:14:00 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:14:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:14:00 +2026-01-09 12:14:00 - 数据库插入成功 +2026-01-09 12:14:00 - 上传线程结束: /mnt/save/warning/alarm_20260109_121359.jpg: 成功! +2026-01-09 12:14:07 - 开始上传图片: /mnt/save/warning/alarm_20260109_121407.jpg +2026-01-09 12:14:08 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:14:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:14:08 +2026-01-09 12:14:09 - 数据库插入成功 +2026-01-09 12:14:09 - 上传线程结束: /mnt/save/warning/alarm_20260109_121407.jpg: 成功! +2026-01-09 12:14:18 - 开始上传图片: /mnt/save/warning/alarm_20260109_121416.jpg +2026-01-09 12:14:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:14:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:14:18 +2026-01-09 12:14:18 - 数据库插入成功 +2026-01-09 12:14:18 - 上传线程结束: /mnt/save/warning/alarm_20260109_121416.jpg: 成功! +2026-01-09 12:14:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:14:32 - 开始上传图片: /mnt/save/warning/alarm_20260109_121431.jpg +2026-01-09 12:14:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:14:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:14:32 +2026-01-09 12:14:33 - 数据库插入成功 +2026-01-09 12:14:33 - 上传线程结束: /mnt/save/warning/alarm_20260109_121431.jpg: 成功! +2026-01-09 12:14:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:15:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:15:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:16:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:16:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:17:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:17:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:18:03 - 开始上传图片: /mnt/save/warning/alarm_20260109_121803.jpg +2026-01-09 12:18:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:18:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:18:03 +2026-01-09 12:18:04 - 数据库插入成功 +2026-01-09 12:18:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_121803.jpg: 成功! +2026-01-09 12:18:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:18:23 - 开始上传图片: /mnt/save/warning/alarm_20260109_121823.jpg +2026-01-09 12:18:23 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:18:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:18:23 +2026-01-09 12:18:24 - 数据库插入成功 +2026-01-09 12:18:24 - 上传线程结束: /mnt/save/warning/alarm_20260109_121823.jpg: 成功! +2026-01-09 12:18:31 - 开始上传图片: /mnt/save/warning/alarm_20260109_121830.jpg +2026-01-09 12:18:31 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:18:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:18:31 +2026-01-09 12:18:32 - 数据库插入成功 +2026-01-09 12:18:32 - 上传线程结束: /mnt/save/warning/alarm_20260109_121830.jpg: 成功! +2026-01-09 12:18:37 - 开始上传图片: /mnt/save/warning/alarm_20260109_121837.jpg +2026-01-09 12:18:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:18:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:18:37 +2026-01-09 12:18:38 - 数据库插入成功 +2026-01-09 12:18:38 - 上传线程结束: /mnt/save/warning/alarm_20260109_121837.jpg: 成功! +2026-01-09 12:18:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:18:57 - 开始上传图片: /mnt/save/warning/alarm_20260109_121856.jpg +2026-01-09 12:18:58 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:18:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:18:58 +2026-01-09 12:18:58 - 数据库插入成功 +2026-01-09 12:18:58 - 上传线程结束: /mnt/save/warning/alarm_20260109_121856.jpg: 成功! +2026-01-09 12:19:22 - 开始上传图片: /mnt/save/warning/alarm_20260109_121920.jpg +2026-01-09 12:19:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:19:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:19:22 +2026-01-09 12:19:22 - 数据库插入成功 +2026-01-09 12:19:22 - 上传线程结束: /mnt/save/warning/alarm_20260109_121920.jpg: 成功! +2026-01-09 12:19:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:19:32 - 开始上传图片: /mnt/save/warning/alarm_20260109_121932.jpg +2026-01-09 12:19:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:19:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:19:32 +2026-01-09 12:19:33 - 数据库插入成功 +2026-01-09 12:19:33 - 上传线程结束: /mnt/save/warning/alarm_20260109_121932.jpg: 成功! +2026-01-09 12:19:38 - 开始上传图片: /mnt/save/warning/alarm_20260109_121936.jpg +2026-01-09 12:19:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:19:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:19:38 +2026-01-09 12:19:39 - 数据库插入成功 +2026-01-09 12:19:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_121936.jpg: 成功! +2026-01-09 12:19:46 - 开始上传图片: /mnt/save/warning/alarm_20260109_121945.jpg +2026-01-09 12:19:46 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:19:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:19:46 +2026-01-09 12:19:47 - 数据库插入成功 +2026-01-09 12:19:47 - 上传线程结束: /mnt/save/warning/alarm_20260109_121945.jpg: 成功! +2026-01-09 12:19:52 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:20:02 - 开始上传图片: /mnt/save/warning/alarm_20260109_122000.jpg +2026-01-09 12:20:02 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:20:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:20:02 +2026-01-09 12:20:03 - 数据库插入成功 +2026-01-09 12:20:03 - 上传线程结束: /mnt/save/warning/alarm_20260109_122000.jpg: 成功! +2026-01-09 12:20:06 - 开始上传图片: /mnt/save/warning/alarm_20260109_122005.jpg +2026-01-09 12:20:06 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:20:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:20:06 +2026-01-09 12:20:07 - 数据库插入成功 +2026-01-09 12:20:07 - 上传线程结束: /mnt/save/warning/alarm_20260109_122005.jpg: 成功! +2026-01-09 12:20:16 - 开始上传图片: /mnt/save/warning/alarm_20260109_122015.jpg +2026-01-09 12:20:16 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:20:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:20:16 +2026-01-09 12:20:17 - 数据库插入成功 +2026-01-09 12:20:17 - 上传线程结束: /mnt/save/warning/alarm_20260109_122015.jpg: 成功! +2026-01-09 12:20:22 - 开始上传图片: /mnt/save/warning/alarm_20260109_122021.jpg +2026-01-09 12:20:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:20:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:20:22 +2026-01-09 12:20:22 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:20:23 - 数据库插入成功 +2026-01-09 12:20:23 - 上传线程结束: /mnt/save/warning/alarm_20260109_122021.jpg: 成功! +2026-01-09 12:20:36 - 开始上传图片: /mnt/save/warning/alarm_20260109_122035.jpg +2026-01-09 12:20:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:20:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:20:36 +2026-01-09 12:20:37 - 数据库插入成功 +2026-01-09 12:20:37 - 上传线程结束: /mnt/save/warning/alarm_20260109_122035.jpg: 成功! +2026-01-09 12:20:44 - 开始上传图片: /mnt/save/warning/alarm_20260109_122044.jpg +2026-01-09 12:20:44 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:20:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:20:44 +2026-01-09 12:20:45 - 数据库插入成功 +2026-01-09 12:20:45 - 上传线程结束: /mnt/save/warning/alarm_20260109_122044.jpg: 成功! +2026-01-09 12:20:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:21:00 - 开始上传图片: /mnt/save/warning/alarm_20260109_122059.jpg +2026-01-09 12:21:00 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:21:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:21:00 +2026-01-09 12:21:01 - 数据库插入成功 +2026-01-09 12:21:01 - 上传线程结束: /mnt/save/warning/alarm_20260109_122059.jpg: 成功! +2026-01-09 12:21:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:21:32 - 开始上传图片: /mnt/save/warning/alarm_20260109_122132.jpg +2026-01-09 12:21:33 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:21:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:21:33 +2026-01-09 12:21:34 - 数据库插入成功 +2026-01-09 12:21:34 - 上传线程结束: /mnt/save/warning/alarm_20260109_122132.jpg: 成功! +2026-01-09 12:21:36 - 开始上传图片: /mnt/save/warning/alarm_20260109_122136.jpg +2026-01-09 12:21:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:21:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:21:37 +2026-01-09 12:21:37 - 数据库插入成功 +2026-01-09 12:21:37 - 上传线程结束: /mnt/save/warning/alarm_20260109_122136.jpg: 成功! +2026-01-09 12:21:44 - 开始上传图片: /mnt/save/warning/alarm_20260109_122144.jpg +2026-01-09 12:21:45 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:21:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:21:45 +2026-01-09 12:21:45 - 数据库插入成功 +2026-01-09 12:21:45 - 上传线程结束: /mnt/save/warning/alarm_20260109_122144.jpg: 成功! +2026-01-09 12:21:49 - 开始上传图片: /mnt/save/warning/alarm_20260109_122148.jpg +2026-01-09 12:21:49 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:21:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:21:49 +2026-01-09 12:21:49 - 数据库插入成功 +2026-01-09 12:21:49 - 上传线程结束: /mnt/save/warning/alarm_20260109_122148.jpg: 成功! +2026-01-09 12:21:53 - 开始上传图片: /mnt/save/warning/alarm_20260109_122152.jpg +2026-01-09 12:21:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:21:53 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:21:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:21:53 +2026-01-09 12:21:53 - 数据库插入成功 +2026-01-09 12:21:53 - 上传线程结束: /mnt/save/warning/alarm_20260109_122152.jpg: 成功! +2026-01-09 12:22:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:22:49 - 开始上传图片: /mnt/save/warning/alarm_20260109_122248.jpg +2026-01-09 12:22:49 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:22:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:22:49 +2026-01-09 12:22:50 - 数据库插入成功 +2026-01-09 12:22:50 - 上传线程结束: /mnt/save/warning/alarm_20260109_122248.jpg: 成功! +2026-01-09 12:22:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:22:55 - 开始上传图片: /mnt/save/warning/alarm_20260109_122254.jpg +2026-01-09 12:22:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:22:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:22:55 +2026-01-09 12:22:56 - 数据库插入成功 +2026-01-09 12:22:56 - 上传线程结束: /mnt/save/warning/alarm_20260109_122254.jpg: 成功! +2026-01-09 12:22:59 - 开始上传图片: /mnt/save/warning/alarm_20260109_122258.jpg +2026-01-09 12:22:59 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:22:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:22:59 +2026-01-09 12:23:00 - 数据库插入成功 +2026-01-09 12:23:00 - 上传线程结束: /mnt/save/warning/alarm_20260109_122258.jpg: 成功! +2026-01-09 12:23:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122308.jpg +2026-01-09 12:23:09 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:23:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:23:09 +2026-01-09 12:23:10 - 数据库插入成功 +2026-01-09 12:23:10 - 上传线程结束: /mnt/save/warning/alarm_20260109_122308.jpg: 成功! +2026-01-09 12:23:17 - 开始上传图片: /mnt/save/warning/alarm_20260109_122316.jpg +2026-01-09 12:23:17 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:23:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:23:17 +2026-01-09 12:23:18 - 数据库插入成功 +2026-01-09 12:23:18 - 上传线程结束: /mnt/save/warning/alarm_20260109_122316.jpg: 成功! +2026-01-09 12:23:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:23:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_122328.jpg +2026-01-09 12:23:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:23:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:23:29 +2026-01-09 12:23:30 - 数据库插入成功 +2026-01-09 12:23:30 - 上传线程结束: /mnt/save/warning/alarm_20260109_122328.jpg: 成功! +2026-01-09 12:23:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:24:18 - 开始上传图片: /mnt/save/warning/alarm_20260109_122417.jpg +2026-01-09 12:24:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:24:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:24:18 +2026-01-09 12:24:19 - 数据库插入成功 +2026-01-09 12:24:19 - 上传线程结束: /mnt/save/warning/alarm_20260109_122417.jpg: 成功! +2026-01-09 12:24:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:24:38 - 开始上传图片: /mnt/save/warning/alarm_20260109_122437.jpg +2026-01-09 12:24:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:24:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:24:38 +2026-01-09 12:24:39 - 数据库插入成功 +2026-01-09 12:24:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_122437.jpg: 成功! +2026-01-09 12:24:42 - 开始上传图片: /mnt/save/warning/alarm_20260109_122440.jpg +2026-01-09 12:24:42 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:24:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:24:42 +2026-01-09 12:24:44 - 数据库插入成功 +2026-01-09 12:24:44 - 上传线程结束: /mnt/save/warning/alarm_20260109_122440.jpg: 成功! +2026-01-09 12:24:48 - 开始上传图片: /mnt/save/warning/alarm_20260109_122448.jpg +2026-01-09 12:24:48 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:24:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:24:48 +2026-01-09 12:24:49 - 数据库插入成功 +2026-01-09 12:24:49 - 上传线程结束: /mnt/save/warning/alarm_20260109_122448.jpg: 成功! +2026-01-09 12:24:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:25:10 - 开始上传图片: /mnt/save/warning/alarm_20260109_122510.jpg +2026-01-09 12:25:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:25:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:25:10 +2026-01-09 12:25:11 - 数据库插入成功 +2026-01-09 12:25:11 - 上传线程结束: /mnt/save/warning/alarm_20260109_122510.jpg: 成功! +2026-01-09 12:25:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:25:45 - 开始上传图片: /mnt/save/warning/alarm_20260109_122544.jpg +2026-01-09 12:25:45 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:25:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:25:45 +2026-01-09 12:25:46 - 数据库插入成功 +2026-01-09 12:25:46 - 上传线程结束: /mnt/save/warning/alarm_20260109_122544.jpg: 成功! +2026-01-09 12:25:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:26:03 - 开始上传图片: /mnt/save/warning/alarm_20260109_122601.jpg +2026-01-09 12:26:06 - 上传图片 /mnt/save/warning/alarm_20260109_122601.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:26:07 - 开始上传图片: /mnt/save/warning/alarm_20260109_122606.jpg +2026-01-09 12:26:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:26:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:26:07 +2026-01-09 12:26:09 - 数据库插入成功 +2026-01-09 12:26:09 - 上传线程结束: /mnt/save/warning/alarm_20260109_122606.jpg: 成功! +2026-01-09 12:26:13 - 开始上传图片: /mnt/save/warning/alarm_20260109_122611.jpg +2026-01-09 12:26:13 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:26:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:26:13 +2026-01-09 12:26:17 - 数据库插入成功 +2026-01-09 12:26:17 - 上传线程结束: /mnt/save/warning/alarm_20260109_122611.jpg: 成功! +2026-01-09 12:26:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:26:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_122627.jpg +2026-01-09 12:26:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:26:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:26:29 +2026-01-09 12:26:30 - 数据库插入成功 +2026-01-09 12:26:30 - 上传线程结束: /mnt/save/warning/alarm_20260109_122627.jpg: 成功! +2026-01-09 12:26:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:27:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:27:31 - 开始上传图片: /mnt/save/warning/alarm_20260109_122730.jpg +2026-01-09 12:27:31 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:27:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:27:31 +2026-01-09 12:27:33 - 数据库插入成功 +2026-01-09 12:27:33 - 上传线程结束: /mnt/save/warning/alarm_20260109_122730.jpg: 成功! +2026-01-09 12:27:37 - 开始上传图片: /mnt/save/warning/alarm_20260109_122737.jpg +2026-01-09 12:27:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:27:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:27:37 +2026-01-09 12:27:38 - 数据库插入成功 +2026-01-09 12:27:38 - 上传线程结束: /mnt/save/warning/alarm_20260109_122737.jpg: 成功! +2026-01-09 12:27:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:28:07 - 开始上传图片: /mnt/save/warning/alarm_20260109_122806.jpg +2026-01-09 12:28:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:28:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:28:07 +2026-01-09 12:28:09 - 数据库插入成功 +2026-01-09 12:28:09 - 上传线程结束: /mnt/save/warning/alarm_20260109_122806.jpg: 成功! +2026-01-09 12:28:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:28:26 - 开始上传图片: /mnt/save/warning/alarm_20260109_122825.jpg +2026-01-09 12:28:26 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:28:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:28:26 +2026-01-09 12:28:27 - 数据库插入成功 +2026-01-09 12:28:27 - 上传线程结束: /mnt/save/warning/alarm_20260109_122825.jpg: 成功! +2026-01-09 12:28:34 - 开始上传图片: /mnt/save/warning/alarm_20260109_122833.jpg +2026-01-09 12:28:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:28:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:28:37 +2026-01-09 12:28:38 - 数据库插入成功 +2026-01-09 12:28:38 - 上传线程结束: /mnt/save/warning/alarm_20260109_122833.jpg: 成功! +2026-01-09 12:28:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:29:00 - 开始上传图片: /mnt/save/warning/alarm_20260109_122859.jpg +2026-01-09 12:29:03 - 上传图片 /mnt/save/warning/alarm_20260109_122859.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:29:04 - 开始上传图片: /mnt/save/warning/alarm_20260109_122903.jpg +2026-01-09 12:29:07 - 上传图片 /mnt/save/warning/alarm_20260109_122903.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:29:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:29:28 - 开始上传图片: /mnt/save/warning/alarm_20260109_122927.jpg +2026-01-09 12:29:30 - 上传图片 /mnt/save/warning/alarm_20260109_122927.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:29:40 - 开始上传图片: /mnt/save/warning/alarm_20260109_122940.jpg +2026-01-09 12:29:42 - 上传图片 /mnt/save/warning/alarm_20260109_122940.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:29:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:30:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:30:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:30:57 - 开始上传图片: /mnt/save/warning/alarm_20260109_123056.jpg +2026-01-09 12:30:57 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:30:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:30:57 +2026-01-09 12:30:58 - 数据库插入成功 +2026-01-09 12:30:58 - 上传线程结束: /mnt/save/warning/alarm_20260109_123056.jpg: 成功! +2026-01-09 12:31:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:31:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:32:19 - 开始上传图片: /mnt/save/warning/alarm_20260109_123217.jpg +2026-01-09 12:32:22 - 上传图片 /mnt/save/warning/alarm_20260109_123217.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:32:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:32:25 - 开始上传图片: /mnt/save/warning/alarm_20260109_123224.jpg +2026-01-09 12:32:26 - 上传图片 /mnt/save/warning/alarm_20260109_123224.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:32:37 - 开始上传图片: /mnt/save/warning/alarm_20260109_123237.jpg +2026-01-09 12:32:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 12:32:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:32:37 +2026-01-09 12:32:38 - 数据库插入成功 +2026-01-09 12:32:38 - 上传线程结束: /mnt/save/warning/alarm_20260109_123237.jpg: 成功! +2026-01-09 12:32:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:33:00 - 开始上传图片: /mnt/save/warning/alarm_20260109_123258.jpg +2026-01-09 12:33:00 - 上传图片 /mnt/save/warning/alarm_20260109_123258.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:33:04 - 开始上传图片: /mnt/save/warning/alarm_20260109_123302.jpg +2026-01-09 12:33:04 - 上传图片 /mnt/save/warning/alarm_20260109_123302.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:33:08 - 开始上传图片: /mnt/save/warning/alarm_20260109_123306.jpg +2026-01-09 12:33:08 - 上传图片 /mnt/save/warning/alarm_20260109_123306.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:33:14 - 开始上传图片: /mnt/save/warning/alarm_20260109_123312.jpg +2026-01-09 12:33:14 - 上传图片 /mnt/save/warning/alarm_20260109_123312.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:33:18 - 开始上传图片: /mnt/save/warning/alarm_20260109_123317.jpg +2026-01-09 12:33:19 - 上传图片 /mnt/save/warning/alarm_20260109_123317.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:33:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:33:26 - 开始上传图片: /mnt/save/warning/alarm_20260109_123325.jpg +2026-01-09 12:33:30 - 上传图片 /mnt/save/warning/alarm_20260109_123325.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:33:36 - 开始上传图片: /mnt/save/warning/alarm_20260109_123335.jpg +2026-01-09 12:33:37 - 上传图片 /mnt/save/warning/alarm_20260109_123335.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:33:50 - 开始上传图片: /mnt/save/warning/alarm_20260109_123349.jpg +2026-01-09 12:33:53 - 上传图片 /mnt/save/warning/alarm_20260109_123349.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:33:53 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:34:02 - 开始上传图片: /mnt/save/warning/alarm_20260109_123401.jpg +2026-01-09 12:34:03 - 上传图片 /mnt/save/warning/alarm_20260109_123401.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:34:10 - 开始上传图片: /mnt/save/warning/alarm_20260109_123409.jpg +2026-01-09 12:34:13 - 上传图片 /mnt/save/warning/alarm_20260109_123409.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:34:23 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 12:34:24 - 开始上传图片: /mnt/save/warning/alarm_20260109_123424.jpg +2026-01-09 12:34:27 - 上传图片 /mnt/save/warning/alarm_20260109_123424.jpg 时出错: [Errno 113] No route to host +2026-01-09 12:34:32 - 开始上传图片: /mnt/save/warning/alarm_20260109_123431.jpg +2026-01-09 12:34:37 - 上传图片 /mnt/save/warning/alarm_20260109_123431.jpg 时出错: timed out +2026-01-09 12:17:09 - ICCID刷新线程启动 +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_121823.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_121945.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122044.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122148.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122328.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122417.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122448.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122510.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122627.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122737.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122927.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123056.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123302.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123312.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123349.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_121803.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_121936.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122021.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122059.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122136.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122152.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122248.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122308.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122940.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_121830.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_121837.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_121920.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122000.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122005.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122035.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122144.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122258.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122316.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122437.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122440.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122606.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122611.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122825.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122833.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122859.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123224.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123306.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123317.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123325.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123335.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_121856.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_121932.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122015.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122132.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122254.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122544.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122601.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122730.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122806.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_122903.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123217.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123237.jpg +2026-01-09 12:17:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_123258.jpg +2026-01-09 12:17:16 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:17:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:17:16 +2026-01-09 12:17:17 - 数据库插入成功 +2026-01-09 12:17:17 - 上传线程结束: /mnt/save/warning/alarm_20260109_121823.jpg: 成功! +2026-01-09 12:17:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:17:17 +2026-01-09 12:17:18 - 数据库插入成功 +2026-01-09 12:17:18 - 上传线程结束: /mnt/save/warning/alarm_20260109_122737.jpg: 成功! +2026-01-09 12:17:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:17:18 +2026-01-09 12:40:45 - 数据库插入成功 +2026-01-09 12:40:45 - 上传线程结束: /mnt/save/warning/alarm_20260109_122510.jpg: 成功! +2026-01-09 12:40:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:45 +2026-01-09 12:40:46 - 数据库插入成功 +2026-01-09 12:40:46 - 上传线程结束: /mnt/save/warning/alarm_20260109_122627.jpg: 成功! +2026-01-09 12:40:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:46 +2026-01-09 12:40:47 - 数据库插入成功 +2026-01-09 12:40:47 - 上传线程结束: /mnt/save/warning/alarm_20260109_123349.jpg: 成功! +2026-01-09 12:40:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:47 +2026-01-09 12:40:48 - 数据库插入成功 +2026-01-09 12:40:48 - 上传线程结束: /mnt/save/warning/alarm_20260109_122927.jpg: 成功! +2026-01-09 12:40:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:48 +2026-01-09 12:40:49 - 数据库插入成功 +2026-01-09 12:40:49 - 上传线程结束: /mnt/save/warning/alarm_20260109_122328.jpg: 成功! +2026-01-09 12:40:49 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:40:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:49 +2026-01-09 12:40:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:49 +2026-01-09 12:40:50 - 数据库插入成功 +2026-01-09 12:40:50 - 上传线程结束: /mnt/save/warning/alarm_20260109_122148.jpg: 成功! +2026-01-09 12:40:50 - 数据库插入成功 +2026-01-09 12:40:50 - 上传线程结束: /mnt/save/warning/alarm_20260109_123302.jpg: 成功! +2026-01-09 12:40:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:50 +2026-01-09 12:40:51 - 数据库插入成功 +2026-01-09 12:40:51 - 上传线程结束: /mnt/save/warning/alarm_20260109_123312.jpg: 成功! +2026-01-09 12:40:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:51 +2026-01-09 12:40:52 - 数据库插入成功 +2026-01-09 12:40:52 - 上传线程结束: /mnt/save/warning/alarm_20260109_122021.jpg: 成功! +2026-01-09 12:40:52 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:52 +2026-01-09 12:40:53 - 数据库插入成功 +2026-01-09 12:40:53 - 上传线程结束: /mnt/save/warning/alarm_20260109_121803.jpg: 成功! +2026-01-09 12:40:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:53 +2026-01-09 12:40:54 - 数据库插入成功 +2026-01-09 12:40:54 - 上传线程结束: /mnt/save/warning/alarm_20260109_122248.jpg: 成功! +2026-01-09 12:40:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:54 +2026-01-09 12:40:55 - 数据库插入成功 +2026-01-09 12:40:55 - 上传线程结束: /mnt/save/warning/alarm_20260109_121936.jpg: 成功! +2026-01-09 12:40:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:55 +2026-01-09 12:40:56 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:40:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:56 +2026-01-09 12:40:56 - 数据库插入成功 +2026-01-09 12:40:56 - 上传线程结束: /mnt/save/warning/alarm_20260109_121837.jpg: 成功! +2026-01-09 12:40:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:56 +2026-01-09 12:40:57 - 数据库插入成功 +2026-01-09 12:40:57 - 上传线程结束: /mnt/save/warning/alarm_20260109_122417.jpg: 成功! +2026-01-09 12:40:57 - 数据库插入成功 +2026-01-09 12:40:57 - 上传线程结束: /mnt/save/warning/alarm_20260109_122308.jpg: 成功! +2026-01-09 12:40:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:57 +2026-01-09 12:40:58 - 数据库插入成功 +2026-01-09 12:40:58 - 上传线程结束: /mnt/save/warning/alarm_20260109_121830.jpg: 成功! +2026-01-09 12:40:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:58 +2026-01-09 12:40:59 - 数据库插入成功 +2026-01-09 12:40:59 - 上传线程结束: /mnt/save/warning/alarm_20260109_122059.jpg: 成功! +2026-01-09 12:40:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:40:59 +2026-01-09 12:41:00 - 数据库插入成功 +2026-01-09 12:41:00 - 上传线程结束: /mnt/save/warning/alarm_20260109_122940.jpg: 成功! +2026-01-09 12:41:00 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:00 +2026-01-09 12:41:01 - 数据库插入成功 +2026-01-09 12:41:01 - 上传线程结束: /mnt/save/warning/alarm_20260109_122152.jpg: 成功! +2026-01-09 12:41:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:01 +2026-01-09 12:41:02 - 数据库插入成功 +2026-01-09 12:41:02 - 上传线程结束: /mnt/save/warning/alarm_20260109_122136.jpg: 成功! +2026-01-09 12:41:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:02 +2026-01-09 12:41:03 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:41:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:03 +2026-01-09 12:41:03 - 数据库插入成功 +2026-01-09 12:41:03 - 上传线程结束: /mnt/save/warning/alarm_20260109_122005.jpg: 成功! +2026-01-09 12:41:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:03 +2026-01-09 12:41:04 - 数据库插入成功 +2026-01-09 12:41:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_122044.jpg: 成功! +2026-01-09 12:41:04 - 数据库插入成功 +2026-01-09 12:41:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_122000.jpg: 成功! +2026-01-09 12:41:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:04 +2026-01-09 12:41:05 - 数据库插入成功 +2026-01-09 12:41:05 - 上传线程结束: /mnt/save/warning/alarm_20260109_122144.jpg: 成功! +2026-01-09 12:41:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:05 +2026-01-09 12:41:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:06 +2026-01-09 12:41:07 - 数据库插入成功 +2026-01-09 12:41:07 - 上传线程结束: /mnt/save/warning/alarm_20260109_122035.jpg: 成功! +2026-01-09 12:41:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:07 +2026-01-09 12:41:08 - 数据库插入成功 +2026-01-09 12:41:08 - 上传线程结束: /mnt/save/warning/alarm_20260109_121920.jpg: 成功! +2026-01-09 12:41:08 - 数据库插入成功 +2026-01-09 12:41:08 - 上传线程结束: /mnt/save/warning/alarm_20260109_122606.jpg: 成功! +2026-01-09 12:41:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:08 +2026-01-09 12:41:09 - 数据库插入成功 +2026-01-09 12:41:09 - 上传线程结束: /mnt/save/warning/alarm_20260109_122258.jpg: 成功! +2026-01-09 12:41:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:09 +2026-01-09 12:41:10 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:41:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:10 +2026-01-09 12:41:10 - 数据库插入成功 +2026-01-09 12:41:10 - 上传线程结束: /mnt/save/warning/alarm_20260109_122825.jpg: 成功! +2026-01-09 12:41:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:10 +2026-01-09 12:41:11 - 数据库插入成功 +2026-01-09 12:41:11 - 上传线程结束: /mnt/save/warning/alarm_20260109_121945.jpg: 成功! +2026-01-09 12:41:11 - 数据库插入成功 +2026-01-09 12:41:11 - 上传线程结束: /mnt/save/warning/alarm_20260109_122440.jpg: 成功! +2026-01-09 12:41:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:11 +2026-01-09 12:41:12 - 数据库插入成功 +2026-01-09 12:41:12 - 上传线程结束: /mnt/save/warning/alarm_20260109_122611.jpg: 成功! +2026-01-09 12:41:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:12 +2026-01-09 12:41:13 - 数据库插入成功 +2026-01-09 12:41:13 - 上传线程结束: /mnt/save/warning/alarm_20260109_122437.jpg: 成功! +2026-01-09 12:41:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:13 +2026-01-09 12:41:14 - 数据库插入成功 +2026-01-09 12:41:14 - 上传线程结束: /mnt/save/warning/alarm_20260109_123306.jpg: 成功! +2026-01-09 12:41:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:14 +2026-01-09 12:41:15 - 数据库插入成功 +2026-01-09 12:41:15 - 上传线程结束: /mnt/save/warning/alarm_20260109_122833.jpg: 成功! +2026-01-09 12:41:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:15 +2026-01-09 12:41:16 - 数据库插入成功 +2026-01-09 12:41:16 - 上传线程结束: /mnt/save/warning/alarm_20260109_122316.jpg: 成功! +2026-01-09 12:41:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:16 +2026-01-09 12:41:17 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:41:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:17 +2026-01-09 12:41:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:17 +2026-01-09 12:41:18 - 数据库插入成功 +2026-01-09 12:41:18 - 上传线程结束: /mnt/save/warning/alarm_20260109_123335.jpg: 成功! +2026-01-09 12:41:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:18 +2026-01-09 12:41:19 - 数据库插入成功 +2026-01-09 12:41:19 - 上传线程结束: /mnt/save/warning/alarm_20260109_123056.jpg: 成功! +2026-01-09 12:41:19 - 数据库插入成功 +2026-01-09 12:41:19 - 上传线程结束: /mnt/save/warning/alarm_20260109_123317.jpg: 成功! +2026-01-09 12:41:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:19 +2026-01-09 12:41:20 - 数据库插入成功 +2026-01-09 12:41:20 - 上传线程结束: /mnt/save/warning/alarm_20260109_123224.jpg: 成功! +2026-01-09 12:41:20 - 数据库插入成功 +2026-01-09 12:41:20 - 上传线程结束: /mnt/save/warning/alarm_20260109_123325.jpg: 成功! +2026-01-09 12:41:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:20 +2026-01-09 12:41:21 - 数据库插入成功 +2026-01-09 12:41:21 - 上传线程结束: /mnt/save/warning/alarm_20260109_122859.jpg: 成功! +2026-01-09 12:41:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:21 +2026-01-09 12:41:22 - 数据库插入成功 +2026-01-09 12:41:22 - 上传线程结束: /mnt/save/warning/alarm_20260109_122015.jpg: 成功! +2026-01-09 12:41:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:22 +2026-01-09 12:41:23 - 数据库插入成功 +2026-01-09 12:41:23 - 上传线程结束: /mnt/save/warning/alarm_20260109_122254.jpg: 成功! +2026-01-09 12:41:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:23 +2026-01-09 12:41:24 - 数据库插入成功 +2026-01-09 12:41:24 - 上传线程结束: /mnt/save/warning/alarm_20260109_121856.jpg: 成功! +2026-01-09 12:41:24 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:41:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:24 +2026-01-09 12:41:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:24 +2026-01-09 12:41:25 - 数据库插入成功 +2026-01-09 12:41:25 - 上传线程结束: /mnt/save/warning/alarm_20260109_122448.jpg: 成功! +2026-01-09 12:41:25 - 数据库插入成功 +2026-01-09 12:41:25 - 上传线程结束: /mnt/save/warning/alarm_20260109_121932.jpg: 成功! +2026-01-09 12:41:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:25 +2026-01-09 12:41:26 - 数据库插入成功 +2026-01-09 12:41:26 - 上传线程结束: /mnt/save/warning/alarm_20260109_122544.jpg: 成功! +2026-01-09 12:41:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:26 +2026-01-09 12:41:27 - 数据库插入成功 +2026-01-09 12:41:27 - 上传线程结束: /mnt/save/warning/alarm_20260109_122601.jpg: 成功! +2026-01-09 12:41:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:27 +2026-01-09 12:41:28 - 数据库插入成功 +2026-01-09 12:41:28 - 上传线程结束: /mnt/save/warning/alarm_20260109_122132.jpg: 成功! +2026-01-09 12:41:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:28 +2026-01-09 12:41:29 - 数据库插入成功 +2026-01-09 12:41:29 - 上传线程结束: /mnt/save/warning/alarm_20260109_123217.jpg: 成功! +2026-01-09 12:41:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:29 +2026-01-09 12:41:30 - 数据库插入成功 +2026-01-09 12:41:30 - 上传线程结束: /mnt/save/warning/alarm_20260109_122806.jpg: 成功! +2026-01-09 12:41:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:30 +2026-01-09 12:41:31 - 数据库插入成功 +2026-01-09 12:41:31 - 上传线程结束: /mnt/save/warning/alarm_20260109_123237.jpg: 成功! +2026-01-09 12:41:31 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:41:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:32 +2026-01-09 12:41:32 - 数据库插入成功 +2026-01-09 12:41:32 - 上传线程结束: /mnt/save/warning/alarm_20260109_122903.jpg: 成功! +2026-01-09 12:41:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:33 +2026-01-09 12:41:33 - 数据库插入成功 +2026-01-09 12:41:33 - 上传线程结束: /mnt/save/warning/alarm_20260109_122730.jpg: 成功! +2026-01-09 12:41:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 12:41:34 +2026-01-09 12:41:34 - 数据库插入成功 +2026-01-09 12:41:34 - 上传线程结束: /mnt/save/warning/alarm_20260109_123258.jpg: 成功! +2026-01-09 12:42:40 - ICCID刷新线程启动 +2026-01-09 12:42:47 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:43:24 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:44:01 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:44:38 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:45:15 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:45:52 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:46:29 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:47:06 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:47:43 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:48:20 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:48:57 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:49:34 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:50:11 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:50:48 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:51:25 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:52:02 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:52:31 - ICCID刷新线程启动 +2026-01-09 12:52:39 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:53:16 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:53:53 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:54:30 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:52:21 - ICCID刷新线程启动 +2026-01-09 12:52:28 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:56:16 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:57:13 - ICCID刷新线程启动 +2026-01-09 12:57:20 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:57:57 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 12:52:22 - ICCID刷新线程启动 +2026-01-09 12:52:22 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:00:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:01:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:01:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:02:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:02:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:03:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:03:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:04:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:04:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:05:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:05:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:06:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:06:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:07:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:07:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:08:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:08:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:09:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:09:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:10:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:10:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:11:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:11:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:12:00 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:12:30 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:13:01 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:13:31 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:14:01 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:14:31 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:15:01 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:15:31 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:16:01 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:16:31 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:17:01 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:17:31 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:18:01 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:18:31 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:19:01 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:19:31 - 读取ICCID异常: [Errno 2] could not open port /dev/ttyUSB2: [Errno 2] No such file or directory: '/dev/ttyUSB2' +2026-01-09 13:20:24 - ICCID刷新线程启动 +2026-01-09 13:20:31 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:20:47 - 开始上传图片: /mnt/save/warning/alarm_20260109_132045.jpg +2026-01-09 13:20:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:20:48 +2026-01-09 13:20:49 - 数据库插入成功 +2026-01-09 13:20:49 - 上传线程结束: /mnt/save/warning/alarm_20260109_132045.jpg: 成功! +2026-01-09 13:21:07 - 开始上传图片: /mnt/save/warning/alarm_20260109_132105.jpg +2026-01-09 13:21:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:21:08 +2026-01-09 13:21:08 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:21:09 - 数据库插入成功 +2026-01-09 13:21:09 - 上传线程结束: /mnt/save/warning/alarm_20260109_132105.jpg: 成功! +2026-01-09 13:21:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_132108.jpg +2026-01-09 13:21:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:21:10 +2026-01-09 13:21:11 - 数据库插入成功 +2026-01-09 13:21:11 - 上传线程结束: /mnt/save/warning/alarm_20260109_132108.jpg: 成功! +2026-01-09 13:21:25 - 开始上传图片: /mnt/save/warning/alarm_20260109_132123.jpg +2026-01-09 13:21:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:21:26 +2026-01-09 13:21:27 - 数据库插入成功 +2026-01-09 13:21:27 - 上传线程结束: /mnt/save/warning/alarm_20260109_132123.jpg: 成功! +2026-01-09 13:21:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_132128.jpg +2026-01-09 13:21:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:21:30 +2026-01-09 13:21:31 - 数据库插入成功 +2026-01-09 13:21:31 - 上传线程结束: /mnt/save/warning/alarm_20260109_132128.jpg: 成功! +2026-01-09 13:21:45 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:22:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_132209.jpg +2026-01-09 13:22:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:22:10 +2026-01-09 13:22:11 - 数据库插入成功 +2026-01-09 13:22:11 - 上传线程结束: /mnt/save/warning/alarm_20260109_132209.jpg: 成功! +2026-01-09 13:22:22 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:22:38 - 开始上传图片: /mnt/save/warning/alarm_20260109_132236.jpg +2026-01-09 13:22:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:22:39 +2026-01-09 13:22:40 - 数据库插入成功 +2026-01-09 13:22:40 - 上传线程结束: /mnt/save/warning/alarm_20260109_132236.jpg: 成功! +2026-01-09 13:22:54 - 开始上传图片: /mnt/save/warning/alarm_20260109_132252.jpg +2026-01-09 13:22:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:22:55 +2026-01-09 13:22:56 - 数据库插入成功 +2026-01-09 13:22:56 - 上传线程结束: /mnt/save/warning/alarm_20260109_132252.jpg: 成功! +2026-01-09 13:22:58 - 开始上传图片: /mnt/save/warning/alarm_20260109_132256.jpg +2026-01-09 13:22:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:22:59 +2026-01-09 13:22:59 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:23:00 - 数据库插入成功 +2026-01-09 13:23:00 - 上传线程结束: /mnt/save/warning/alarm_20260109_132256.jpg: 成功! +2026-01-09 13:23:04 - 开始上传图片: /mnt/save/warning/alarm_20260109_132303.jpg +2026-01-09 13:23:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:23:05 +2026-01-09 13:23:06 - 数据库插入成功 +2026-01-09 13:23:06 - 上传线程结束: /mnt/save/warning/alarm_20260109_132303.jpg: 成功! +2026-01-09 13:23:12 - 开始上传图片: /mnt/save/warning/alarm_20260109_132312.jpg +2026-01-09 13:23:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:23:13 +2026-01-09 13:23:14 - 数据库插入成功 +2026-01-09 13:23:14 - 上传线程结束: /mnt/save/warning/alarm_20260109_132312.jpg: 成功! +2026-01-09 13:23:37 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:23:42 - 开始上传图片: /mnt/save/warning/alarm_20260109_132341.jpg +2026-01-09 13:23:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:23:43 +2026-01-09 13:23:44 - 数据库插入成功 +2026-01-09 13:23:44 - 上传线程结束: /mnt/save/warning/alarm_20260109_132341.jpg: 成功! +2026-01-09 13:23:52 - 开始上传图片: /mnt/save/warning/alarm_20260109_132352.jpg +2026-01-09 13:23:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:23:53 +2026-01-09 13:23:54 - 数据库插入成功 +2026-01-09 13:23:54 - 上传线程结束: /mnt/save/warning/alarm_20260109_132352.jpg: 成功! +2026-01-09 13:23:56 - 开始上传图片: /mnt/save/warning/alarm_20260109_132356.jpg +2026-01-09 13:23:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:23:57 +2026-01-09 13:23:58 - 数据库插入成功 +2026-01-09 13:23:58 - 上传线程结束: /mnt/save/warning/alarm_20260109_132356.jpg: 成功! +2026-01-09 13:24:12 - 开始上传图片: /mnt/save/warning/alarm_20260109_132412.jpg +2026-01-09 13:24:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:24:14 +2026-01-09 13:24:14 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:24:14 - 数据库插入成功 +2026-01-09 13:24:14 - 上传线程结束: /mnt/save/warning/alarm_20260109_132412.jpg: 成功! +2026-01-09 13:24:47 - 开始上传图片: /mnt/save/warning/alarm_20260109_132446.jpg +2026-01-09 13:24:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:24:48 +2026-01-09 13:24:48 - 数据库插入成功 +2026-01-09 13:24:48 - 上传线程结束: /mnt/save/warning/alarm_20260109_132446.jpg: 成功! +2026-01-09 13:24:51 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:25:11 - 开始上传图片: /mnt/save/warning/alarm_20260109_132510.jpg +2026-01-09 13:25:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:25:12 +2026-01-09 13:25:13 - 数据库插入成功 +2026-01-09 13:25:13 - 上传线程结束: /mnt/save/warning/alarm_20260109_132510.jpg: 成功! +2026-01-09 13:25:27 - 开始上传图片: /mnt/save/warning/alarm_20260109_132525.jpg +2026-01-09 13:25:28 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:25:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:25:28 +2026-01-09 13:25:29 - 数据库插入成功 +2026-01-09 13:25:29 - 上传线程结束: /mnt/save/warning/alarm_20260109_132525.jpg: 成功! +2026-01-09 13:25:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_132539.jpg +2026-01-09 13:25:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:25:40 +2026-01-09 13:25:41 - 数据库插入成功 +2026-01-09 13:25:41 - 上传线程结束: /mnt/save/warning/alarm_20260109_132539.jpg: 成功! +2026-01-09 13:25:45 - 开始上传图片: /mnt/save/warning/alarm_20260109_132545.jpg +2026-01-09 13:25:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:25:46 +2026-01-09 13:25:48 - 数据库插入成功 +2026-01-09 13:25:48 - 上传线程结束: /mnt/save/warning/alarm_20260109_132545.jpg: 成功! +2026-01-09 13:26:01 - 开始上传图片: /mnt/save/warning/alarm_20260109_132601.jpg +2026-01-09 13:26:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:26:02 +2026-01-09 13:26:04 - 数据库插入成功 +2026-01-09 13:26:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_132601.jpg: 成功! +2026-01-09 13:26:05 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:26:05 - 开始上传图片: /mnt/save/warning/alarm_20260109_132605.jpg +2026-01-09 13:26:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:26:07 +2026-01-09 13:26:07 - 数据库插入成功 +2026-01-09 13:26:07 - 上传线程结束: /mnt/save/warning/alarm_20260109_132605.jpg: 成功! +2026-01-09 13:26:36 - 开始上传图片: /mnt/save/warning/alarm_20260109_132635.jpg +2026-01-09 13:26:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:26:37 +2026-01-09 13:26:38 - 数据库插入成功 +2026-01-09 13:26:38 - 上传线程结束: /mnt/save/warning/alarm_20260109_132635.jpg: 成功! +2026-01-09 13:26:42 - 读取ICCID成功: 898604581824D0366321 +2026-01-09 13:26:48 - 开始上传图片: /mnt/save/warning/alarm_20260109_132647.jpg +2026-01-09 13:26:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:26:49 +2026-01-09 13:26:50 - 数据库插入成功 +2026-01-09 13:26:50 - 上传线程结束: /mnt/save/warning/alarm_20260109_132647.jpg: 成功! +2026-01-09 13:27:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:27:24 - 开始上传图片: /mnt/save/warning/alarm_20260109_132723.jpg +2026-01-09 13:27:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:27:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:27:24 +2026-01-09 13:27:24 - 数据库插入成功 +2026-01-09 13:27:24 - 上传线程结束: /mnt/save/warning/alarm_20260109_132723.jpg: 成功! +2026-01-09 13:27:38 - 开始上传图片: /mnt/save/warning/alarm_20260109_132737.jpg +2026-01-09 13:27:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:27:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:27:38 +2026-01-09 13:27:39 - 数据库插入成功 +2026-01-09 13:27:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_132737.jpg: 成功! +2026-01-09 13:27:40 - 开始上传图片: /mnt/save/warning/alarm_20260109_132740.jpg +2026-01-09 13:27:40 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:27:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:27:40 +2026-01-09 13:27:41 - 数据库插入成功 +2026-01-09 13:27:41 - 上传线程结束: /mnt/save/warning/alarm_20260109_132740.jpg: 成功! +2026-01-09 13:27:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:28:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:28:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_132828.jpg +2026-01-09 13:28:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:28:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:28:29 +2026-01-09 13:28:29 - 数据库插入成功 +2026-01-09 13:28:29 - 上传线程结束: /mnt/save/warning/alarm_20260109_132828.jpg: 成功! +2026-01-09 13:28:33 - 开始上传图片: /mnt/save/warning/alarm_20260109_132831.jpg +2026-01-09 13:28:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:28:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:28:34 +2026-01-09 13:28:35 - 数据库插入成功 +2026-01-09 13:28:35 - 上传线程结束: /mnt/save/warning/alarm_20260109_132831.jpg: 成功! +2026-01-09 13:28:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_132839.jpg +2026-01-09 13:28:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:28:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:28:39 +2026-01-09 13:28:39 - 数据库插入成功 +2026-01-09 13:28:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_132839.jpg: 成功! +2026-01-09 13:28:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:28:45 - 开始上传图片: /mnt/save/warning/alarm_20260109_132843.jpg +2026-01-09 13:28:45 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:28:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:28:45 +2026-01-09 13:28:45 - 数据库插入成功 +2026-01-09 13:28:45 - 上传线程结束: /mnt/save/warning/alarm_20260109_132843.jpg: 成功! +2026-01-09 13:28:55 - 开始上传图片: /mnt/save/warning/alarm_20260109_132855.jpg +2026-01-09 13:28:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:28:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:28:55 +2026-01-09 13:28:56 - 数据库插入成功 +2026-01-09 13:28:56 - 上传线程结束: /mnt/save/warning/alarm_20260109_132855.jpg: 成功! +2026-01-09 13:28:59 - 开始上传图片: /mnt/save/warning/alarm_20260109_132859.jpg +2026-01-09 13:29:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:29:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:29:01 +2026-01-09 13:29:01 - 数据库插入成功 +2026-01-09 13:29:01 - 上传线程结束: /mnt/save/warning/alarm_20260109_132859.jpg: 成功! +2026-01-09 13:29:12 - 开始上传图片: /mnt/save/warning/alarm_20260109_132911.jpg +2026-01-09 13:29:12 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:29:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:29:12 +2026-01-09 13:29:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:29:12 - 数据库插入成功 +2026-01-09 13:29:12 - 上传线程结束: /mnt/save/warning/alarm_20260109_132911.jpg: 成功! +2026-01-09 13:29:30 - 开始上传图片: /mnt/save/warning/alarm_20260109_132929.jpg +2026-01-09 13:29:30 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:29:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:29:30 +2026-01-09 13:29:30 - 数据库插入成功 +2026-01-09 13:29:30 - 上传线程结束: /mnt/save/warning/alarm_20260109_132929.jpg: 成功! +2026-01-09 13:29:38 - 开始上传图片: /mnt/save/warning/alarm_20260109_132936.jpg +2026-01-09 13:29:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:29:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:29:38 +2026-01-09 13:29:38 - 数据库插入成功 +2026-01-09 13:29:38 - 上传线程结束: /mnt/save/warning/alarm_20260109_132936.jpg: 成功! +2026-01-09 13:29:40 - 开始上传图片: /mnt/save/warning/alarm_20260109_132940.jpg +2026-01-09 13:29:40 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:29:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:29:40 +2026-01-09 13:29:40 - 数据库插入成功 +2026-01-09 13:29:40 - 上传线程结束: /mnt/save/warning/alarm_20260109_132940.jpg: 成功! +2026-01-09 13:29:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:29:46 - 开始上传图片: /mnt/save/warning/alarm_20260109_132944.jpg +2026-01-09 13:29:46 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:29:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:29:46 +2026-01-09 13:29:46 - 数据库插入成功 +2026-01-09 13:29:46 - 上传线程结束: /mnt/save/warning/alarm_20260109_132944.jpg: 成功! +2026-01-09 13:29:58 - 开始上传图片: /mnt/save/warning/alarm_20260109_132957.jpg +2026-01-09 13:29:58 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:29:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:29:58 +2026-01-09 13:29:58 - 数据库插入成功 +2026-01-09 13:29:58 - 上传线程结束: /mnt/save/warning/alarm_20260109_132957.jpg: 成功! +2026-01-09 13:30:04 - 开始上传图片: /mnt/save/warning/alarm_20260109_133002.jpg +2026-01-09 13:30:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:30:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:30:04 +2026-01-09 13:30:04 - 数据库插入成功 +2026-01-09 13:30:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_133002.jpg: 成功! +2026-01-09 13:30:12 - 开始上传图片: /mnt/save/warning/alarm_20260109_133011.jpg +2026-01-09 13:30:12 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:30:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:30:12 +2026-01-09 13:30:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:30:12 - 数据库插入成功 +2026-01-09 13:30:12 - 上传线程结束: /mnt/save/warning/alarm_20260109_133011.jpg: 成功! +2026-01-09 13:30:26 - 开始上传图片: /mnt/save/warning/alarm_20260109_133026.jpg +2026-01-09 13:30:26 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:30:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:30:26 +2026-01-09 13:30:26 - 数据库插入成功 +2026-01-09 13:30:26 - 上传线程结束: /mnt/save/warning/alarm_20260109_133026.jpg: 成功! +2026-01-09 13:30:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:30:46 - 开始上传图片: /mnt/save/warning/alarm_20260109_133045.jpg +2026-01-09 13:30:46 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:30:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:30:46 +2026-01-09 13:30:47 - 数据库插入成功 +2026-01-09 13:30:47 - 上传线程结束: /mnt/save/warning/alarm_20260109_133045.jpg: 成功! +2026-01-09 13:30:58 - 开始上传图片: /mnt/save/warning/alarm_20260109_133057.jpg +2026-01-09 13:30:59 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:30:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:30:59 +2026-01-09 13:31:00 - 数据库插入成功 +2026-01-09 13:31:00 - 上传线程结束: /mnt/save/warning/alarm_20260109_133057.jpg: 成功! +2026-01-09 13:31:02 - 开始上传图片: /mnt/save/warning/alarm_20260109_133101.jpg +2026-01-09 13:31:02 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:02 +2026-01-09 13:31:03 - 数据库插入成功 +2026-01-09 13:31:03 - 上传线程结束: /mnt/save/warning/alarm_20260109_133101.jpg: 成功! +2026-01-09 13:31:08 - 开始上传图片: /mnt/save/warning/alarm_20260109_133108.jpg +2026-01-09 13:31:08 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:08 +2026-01-09 13:31:08 - 数据库插入成功 +2026-01-09 13:31:08 - 上传线程结束: /mnt/save/warning/alarm_20260109_133108.jpg: 成功! +2026-01-09 13:31:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:31:16 - 开始上传图片: /mnt/save/warning/alarm_20260109_133116.jpg +2026-01-09 13:31:16 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:16 +2026-01-09 13:31:16 - 数据库插入成功 +2026-01-09 13:31:16 - 上传线程结束: /mnt/save/warning/alarm_20260109_133116.jpg: 成功! +2026-01-09 13:31:24 - 开始上传图片: /mnt/save/warning/alarm_20260109_133124.jpg +2026-01-09 13:31:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:24 +2026-01-09 13:31:25 - 数据库插入成功 +2026-01-09 13:31:25 - 上传线程结束: /mnt/save/warning/alarm_20260109_133124.jpg: 成功! +2026-01-09 13:31:30 - 开始上传图片: /mnt/save/warning/alarm_20260109_133128.jpg +2026-01-09 13:31:30 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:30 +2026-01-09 13:31:31 - 数据库插入成功 +2026-01-09 13:31:31 - 上传线程结束: /mnt/save/warning/alarm_20260109_133128.jpg: 成功! +2026-01-09 13:31:34 - 开始上传图片: /mnt/save/warning/alarm_20260109_133133.jpg +2026-01-09 13:31:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:34 +2026-01-09 13:31:36 - 数据库插入成功 +2026-01-09 13:31:36 - 上传线程结束: /mnt/save/warning/alarm_20260109_133133.jpg: 成功! +2026-01-09 13:31:38 - 开始上传图片: /mnt/save/warning/alarm_20260109_133136.jpg +2026-01-09 13:31:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:38 +2026-01-09 13:31:39 - 数据库插入成功 +2026-01-09 13:31:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_133136.jpg: 成功! +2026-01-09 13:31:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:31:42 - 开始上传图片: /mnt/save/warning/alarm_20260109_133141.jpg +2026-01-09 13:31:42 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:42 +2026-01-09 13:31:43 - 数据库插入成功 +2026-01-09 13:31:43 - 上传线程结束: /mnt/save/warning/alarm_20260109_133141.jpg: 成功! +2026-01-09 13:31:50 - 开始上传图片: /mnt/save/warning/alarm_20260109_133149.jpg +2026-01-09 13:31:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:50 +2026-01-09 13:31:52 - 数据库插入成功 +2026-01-09 13:31:52 - 上传线程结束: /mnt/save/warning/alarm_20260109_133149.jpg: 成功! +2026-01-09 13:31:58 - 开始上传图片: /mnt/save/warning/alarm_20260109_133157.jpg +2026-01-09 13:31:58 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:31:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:31:58 +2026-01-09 13:31:59 - 数据库插入成功 +2026-01-09 13:31:59 - 上传线程结束: /mnt/save/warning/alarm_20260109_133157.jpg: 成功! +2026-01-09 13:32:08 - 开始上传图片: /mnt/save/warning/alarm_20260109_133207.jpg +2026-01-09 13:32:08 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:32:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:32:08 +2026-01-09 13:32:09 - 数据库插入成功 +2026-01-09 13:32:09 - 上传线程结束: /mnt/save/warning/alarm_20260109_133207.jpg: 成功! +2026-01-09 13:32:10 - 开始上传图片: /mnt/save/warning/alarm_20260109_133210.jpg +2026-01-09 13:32:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:32:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:32:10 +2026-01-09 13:32:11 - 数据库插入成功 +2026-01-09 13:32:11 - 上传线程结束: /mnt/save/warning/alarm_20260109_133210.jpg: 成功! +2026-01-09 13:32:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:32:14 - 开始上传图片: /mnt/save/warning/alarm_20260109_133214.jpg +2026-01-09 13:32:14 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:32:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:32:14 +2026-01-09 13:32:15 - 数据库插入成功 +2026-01-09 13:32:15 - 上传线程结束: /mnt/save/warning/alarm_20260109_133214.jpg: 成功! +2026-01-09 13:32:16 - 开始上传图片: /mnt/save/warning/alarm_20260109_133216.jpg +2026-01-09 13:32:17 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:32:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:32:17 +2026-01-09 13:32:17 - 数据库插入成功 +2026-01-09 13:32:17 - 上传线程结束: /mnt/save/warning/alarm_20260109_133216.jpg: 成功! +2026-01-09 13:32:25 - 开始上传图片: /mnt/save/warning/alarm_20260109_133224.jpg +2026-01-09 13:32:25 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:32:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:32:25 +2026-01-09 13:32:25 - 数据库插入成功 +2026-01-09 13:32:25 - 上传线程结束: /mnt/save/warning/alarm_20260109_133224.jpg: 成功! +2026-01-09 13:32:31 - 开始上传图片: /mnt/save/warning/alarm_20260109_133231.jpg +2026-01-09 13:32:31 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:32:31 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:32:31 +2026-01-09 13:32:32 - 数据库插入成功 +2026-01-09 13:32:32 - 上传线程结束: /mnt/save/warning/alarm_20260109_133231.jpg: 成功! +2026-01-09 13:32:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_133238.jpg +2026-01-09 13:32:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:32:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:32:39 +2026-01-09 13:32:39 - 数据库插入成功 +2026-01-09 13:32:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_133238.jpg: 成功! +2026-01-09 13:32:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:33:01 - 开始上传图片: /mnt/save/warning/alarm_20260109_133301.jpg +2026-01-09 13:33:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:01 +2026-01-09 13:33:02 - 数据库插入成功 +2026-01-09 13:33:02 - 上传线程结束: /mnt/save/warning/alarm_20260109_133301.jpg: 成功! +2026-01-09 13:33:11 - 开始上传图片: /mnt/save/warning/alarm_20260109_133311.jpg +2026-01-09 13:33:11 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:11 +2026-01-09 13:33:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:33:14 - 数据库插入成功 +2026-01-09 13:33:15 - 上传线程结束: /mnt/save/warning/alarm_20260109_133311.jpg: 成功! +2026-01-09 13:33:19 - 开始上传图片: /mnt/save/warning/alarm_20260109_133318.jpg +2026-01-09 13:33:19 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:19 +2026-01-09 13:33:19 - 数据库插入成功 +2026-01-09 13:33:19 - 上传线程结束: /mnt/save/warning/alarm_20260109_133318.jpg: 成功! +2026-01-09 13:33:23 - 开始上传图片: /mnt/save/warning/alarm_20260109_133322.jpg +2026-01-09 13:33:23 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:23 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:23 +2026-01-09 13:33:23 - 数据库插入成功 +2026-01-09 13:33:23 - 上传线程结束: /mnt/save/warning/alarm_20260109_133322.jpg: 成功! +2026-01-09 13:33:35 - 开始上传图片: /mnt/save/warning/alarm_20260109_133335.jpg +2026-01-09 13:33:35 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:35 +2026-01-09 13:33:35 - 数据库插入成功 +2026-01-09 13:33:35 - 上传线程结束: /mnt/save/warning/alarm_20260109_133335.jpg: 成功! +2026-01-09 13:33:41 - 开始上传图片: /mnt/save/warning/alarm_20260109_133339.jpg +2026-01-09 13:33:41 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:41 +2026-01-09 13:33:41 - 数据库插入成功 +2026-01-09 13:33:41 - 上传线程结束: /mnt/save/warning/alarm_20260109_133339.jpg: 成功! +2026-01-09 13:33:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:33:47 - 开始上传图片: /mnt/save/warning/alarm_20260109_133346.jpg +2026-01-09 13:33:47 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:47 +2026-01-09 13:33:48 - 数据库插入成功 +2026-01-09 13:33:48 - 上传线程结束: /mnt/save/warning/alarm_20260109_133346.jpg: 成功! +2026-01-09 13:33:53 - 开始上传图片: /mnt/save/warning/alarm_20260109_133352.jpg +2026-01-09 13:33:53 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:53 +2026-01-09 13:33:54 - 数据库插入成功 +2026-01-09 13:33:54 - 上传线程结束: /mnt/save/warning/alarm_20260109_133352.jpg: 成功! +2026-01-09 13:33:55 - 开始上传图片: /mnt/save/warning/alarm_20260109_133355.jpg +2026-01-09 13:33:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:55 +2026-01-09 13:33:56 - 数据库插入成功 +2026-01-09 13:33:56 - 上传线程结束: /mnt/save/warning/alarm_20260109_133355.jpg: 成功! +2026-01-09 13:33:59 - 开始上传图片: /mnt/save/warning/alarm_20260109_133359.jpg +2026-01-09 13:33:59 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:33:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:33:59 +2026-01-09 13:34:00 - 数据库插入成功 +2026-01-09 13:34:00 - 上传线程结束: /mnt/save/warning/alarm_20260109_133359.jpg: 成功! +2026-01-09 13:34:03 - 开始上传图片: /mnt/save/warning/alarm_20260109_133402.jpg +2026-01-09 13:34:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:34:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:34:03 +2026-01-09 13:34:04 - 数据库插入成功 +2026-01-09 13:34:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_133402.jpg: 成功! +2026-01-09 13:34:05 - 开始上传图片: /mnt/save/warning/alarm_20260109_133404.jpg +2026-01-09 13:34:05 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:34:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:34:05 +2026-01-09 13:34:06 - 数据库插入成功 +2026-01-09 13:34:06 - 上传线程结束: /mnt/save/warning/alarm_20260109_133404.jpg: 成功! +2026-01-09 13:34:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_133408.jpg +2026-01-09 13:34:09 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:34:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:34:09 +2026-01-09 13:34:10 - 数据库插入成功 +2026-01-09 13:34:10 - 上传线程结束: /mnt/save/warning/alarm_20260109_133408.jpg: 成功! +2026-01-09 13:34:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:34:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_133429.jpg +2026-01-09 13:34:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:34:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:34:29 +2026-01-09 13:34:30 - 数据库插入成功 +2026-01-09 13:34:30 - 上传线程结束: /mnt/save/warning/alarm_20260109_133429.jpg: 成功! +2026-01-09 13:34:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:35:04 - 开始上传图片: /mnt/save/warning/alarm_20260109_133503.jpg +2026-01-09 13:35:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:35:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:35:04 +2026-01-09 13:35:04 - 数据库插入成功 +2026-01-09 13:35:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_133503.jpg: 成功! +2026-01-09 13:35:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:35:18 - 开始上传图片: /mnt/save/warning/alarm_20260109_133518.jpg +2026-01-09 13:35:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:35:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:35:18 +2026-01-09 13:35:18 - 数据库插入成功 +2026-01-09 13:35:18 - 上传线程结束: /mnt/save/warning/alarm_20260109_133518.jpg: 成功! +2026-01-09 13:35:22 - 开始上传图片: /mnt/save/warning/alarm_20260109_133521.jpg +2026-01-09 13:35:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:35:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:35:22 +2026-01-09 13:35:22 - 数据库插入成功 +2026-01-09 13:35:22 - 上传线程结束: /mnt/save/warning/alarm_20260109_133521.jpg: 成功! +2026-01-09 13:35:32 - 开始上传图片: /mnt/save/warning/alarm_20260109_133531.jpg +2026-01-09 13:35:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:35:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:35:32 +2026-01-09 13:35:32 - 数据库插入成功 +2026-01-09 13:35:32 - 上传线程结束: /mnt/save/warning/alarm_20260109_133531.jpg: 成功! +2026-01-09 13:35:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:35:44 - 开始上传图片: /mnt/save/warning/alarm_20260109_133542.jpg +2026-01-09 13:35:44 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:35:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:35:44 +2026-01-09 13:35:44 - 数据库插入成功 +2026-01-09 13:35:44 - 上传线程结束: /mnt/save/warning/alarm_20260109_133542.jpg: 成功! +2026-01-09 13:35:46 - 开始上传图片: /mnt/save/warning/alarm_20260109_133546.jpg +2026-01-09 13:35:46 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:35:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:35:46 +2026-01-09 13:35:48 - 数据库插入成功 +2026-01-09 13:35:48 - 上传线程结束: /mnt/save/warning/alarm_20260109_133546.jpg: 成功! +2026-01-09 13:35:54 - 开始上传图片: /mnt/save/warning/alarm_20260109_133553.jpg +2026-01-09 13:35:54 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:35:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:35:54 +2026-01-09 13:35:55 - 数据库插入成功 +2026-01-09 13:35:55 - 上传线程结束: /mnt/save/warning/alarm_20260109_133553.jpg: 成功! +2026-01-09 13:36:06 - 开始上传图片: /mnt/save/warning/alarm_20260109_133606.jpg +2026-01-09 13:36:06 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:36:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:36:06 +2026-01-09 13:36:07 - 数据库插入成功 +2026-01-09 13:36:07 - 上传线程结束: /mnt/save/warning/alarm_20260109_133606.jpg: 成功! +2026-01-09 13:36:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:36:12 - 开始上传图片: /mnt/save/warning/alarm_20260109_133611.jpg +2026-01-09 13:36:12 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:36:12 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:36:12 +2026-01-09 13:36:13 - 数据库插入成功 +2026-01-09 13:36:13 - 上传线程结束: /mnt/save/warning/alarm_20260109_133611.jpg: 成功! +2026-01-09 13:36:26 - 开始上传图片: /mnt/save/warning/alarm_20260109_133625.jpg +2026-01-09 13:36:27 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:36:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:36:27 +2026-01-09 13:36:27 - 数据库插入成功 +2026-01-09 13:36:27 - 上传线程结束: /mnt/save/warning/alarm_20260109_133625.jpg: 成功! +2026-01-09 13:36:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:36:45 - 开始上传图片: /mnt/save/warning/alarm_20260109_133643.jpg +2026-01-09 13:36:45 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:36:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:36:45 +2026-01-09 13:36:45 - 数据库插入成功 +2026-01-09 13:36:45 - 上传线程结束: /mnt/save/warning/alarm_20260109_133643.jpg: 成功! +2026-01-09 13:37:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:37:33 - 开始上传图片: /mnt/save/warning/alarm_20260109_133731.jpg +2026-01-09 13:37:33 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:37:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:37:33 +2026-01-09 13:37:34 - 数据库插入成功 +2026-01-09 13:37:34 - 上传线程结束: /mnt/save/warning/alarm_20260109_133731.jpg: 成功! +2026-01-09 13:37:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_133738.jpg +2026-01-09 13:37:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:37:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:37:39 +2026-01-09 13:37:39 - 数据库插入成功 +2026-01-09 13:37:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_133738.jpg: 成功! +2026-01-09 13:37:42 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:38:12 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:38:22 - 开始上传图片: /mnt/save/warning/alarm_20260109_133820.jpg +2026-01-09 13:38:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:38:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:38:22 +2026-01-09 13:38:22 - 数据库插入成功 +2026-01-09 13:38:22 - 上传线程结束: /mnt/save/warning/alarm_20260109_133820.jpg: 成功! +2026-01-09 13:38:26 - 开始上传图片: /mnt/save/warning/alarm_20260109_133826.jpg +2026-01-09 13:38:26 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:38:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:38:26 +2026-01-09 13:38:26 - 数据库插入成功 +2026-01-09 13:38:26 - 上传线程结束: /mnt/save/warning/alarm_20260109_133826.jpg: 成功! +2026-01-09 13:38:34 - 开始上传图片: /mnt/save/warning/alarm_20260109_133833.jpg +2026-01-09 13:38:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:38:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:38:34 +2026-01-09 13:38:34 - 数据库插入成功 +2026-01-09 13:38:34 - 上传线程结束: /mnt/save/warning/alarm_20260109_133833.jpg: 成功! +2026-01-09 13:38:40 - 开始上传图片: /mnt/save/warning/alarm_20260109_133838.jpg +2026-01-09 13:38:40 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:38:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:38:40 +2026-01-09 13:38:40 - 数据库插入成功 +2026-01-09 13:38:40 - 上传线程结束: /mnt/save/warning/alarm_20260109_133838.jpg: 成功! +2026-01-09 13:38:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:38:54 - 开始上传图片: /mnt/save/warning/alarm_20260109_133853.jpg +2026-01-09 13:38:54 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:38:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:38:54 +2026-01-09 13:38:54 - 数据库插入成功 +2026-01-09 13:38:54 - 上传线程结束: /mnt/save/warning/alarm_20260109_133853.jpg: 成功! +2026-01-09 13:39:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:39:30 - 开始上传图片: /mnt/save/warning/alarm_20260109_133930.jpg +2026-01-09 13:39:30 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:39:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:39:30 +2026-01-09 13:39:31 - 数据库插入成功 +2026-01-09 13:39:31 - 上传线程结束: /mnt/save/warning/alarm_20260109_133930.jpg: 成功! +2026-01-09 13:39:32 - 开始上传图片: /mnt/save/warning/alarm_20260109_133932.jpg +2026-01-09 13:39:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:39:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:39:32 +2026-01-09 13:39:33 - 数据库插入成功 +2026-01-09 13:39:33 - 上传线程结束: /mnt/save/warning/alarm_20260109_133932.jpg: 成功! +2026-01-09 13:39:36 - 开始上传图片: /mnt/save/warning/alarm_20260109_133936.jpg +2026-01-09 13:39:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:39:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:39:36 +2026-01-09 13:39:37 - 数据库插入成功 +2026-01-09 13:39:37 - 上传线程结束: /mnt/save/warning/alarm_20260109_133936.jpg: 成功! +2026-01-09 13:39:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:39:46 - 开始上传图片: /mnt/save/warning/alarm_20260109_133945.jpg +2026-01-09 13:39:46 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:39:46 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:39:46 +2026-01-09 13:39:47 - 数据库插入成功 +2026-01-09 13:39:47 - 上传线程结束: /mnt/save/warning/alarm_20260109_133945.jpg: 成功! +2026-01-09 13:39:48 - 开始上传图片: /mnt/save/warning/alarm_20260109_133947.jpg +2026-01-09 13:39:48 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:39:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:39:48 +2026-01-09 13:39:49 - 数据库插入成功 +2026-01-09 13:39:49 - 上传线程结束: /mnt/save/warning/alarm_20260109_133947.jpg: 成功! +2026-01-09 13:39:56 - 开始上传图片: /mnt/save/warning/alarm_20260109_133955.jpg +2026-01-09 13:39:56 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:39:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:39:56 +2026-01-09 13:39:57 - 数据库插入成功 +2026-01-09 13:39:57 - 上传线程结束: /mnt/save/warning/alarm_20260109_133955.jpg: 成功! +2026-01-09 13:40:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_134007.jpg +2026-01-09 13:40:09 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:40:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:40:09 +2026-01-09 13:40:09 - 数据库插入成功 +2026-01-09 13:40:09 - 上传线程结束: /mnt/save/warning/alarm_20260109_134007.jpg: 成功! +2026-01-09 13:40:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:40:15 - 开始上传图片: /mnt/save/warning/alarm_20260109_134014.jpg +2026-01-09 13:40:15 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:40:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:40:15 +2026-01-09 13:40:15 - 数据库插入成功 +2026-01-09 13:40:15 - 上传线程结束: /mnt/save/warning/alarm_20260109_134014.jpg: 成功! +2026-01-09 13:40:21 - 开始上传图片: /mnt/save/warning/alarm_20260109_134019.jpg +2026-01-09 13:40:21 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:40:21 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:40:21 +2026-01-09 13:40:21 - 数据库插入成功 +2026-01-09 13:40:21 - 上传线程结束: /mnt/save/warning/alarm_20260109_134019.jpg: 成功! +2026-01-09 13:40:33 - 开始上传图片: /mnt/save/warning/alarm_20260109_134033.jpg +2026-01-09 13:40:33 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:40:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:40:33 +2026-01-09 13:40:33 - 数据库插入成功 +2026-01-09 13:40:33 - 上传线程结束: /mnt/save/warning/alarm_20260109_134033.jpg: 成功! +2026-01-09 13:40:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_134037.jpg +2026-01-09 13:40:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:40:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:40:39 +2026-01-09 13:40:39 - 数据库插入成功 +2026-01-09 13:40:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_134037.jpg: 成功! +2026-01-09 13:40:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:40:47 - 开始上传图片: /mnt/save/warning/alarm_20260109_134046.jpg +2026-01-09 13:40:47 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:40:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:40:47 +2026-01-09 13:40:47 - 数据库插入成功 +2026-01-09 13:40:47 - 上传线程结束: /mnt/save/warning/alarm_20260109_134046.jpg: 成功! +2026-01-09 13:40:53 - 开始上传图片: /mnt/save/warning/alarm_20260109_134052.jpg +2026-01-09 13:40:53 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:40:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:40:53 +2026-01-09 13:40:53 - 数据库插入成功 +2026-01-09 13:40:53 - 上传线程结束: /mnt/save/warning/alarm_20260109_134052.jpg: 成功! +2026-01-09 13:40:55 - 开始上传图片: /mnt/save/warning/alarm_20260109_134054.jpg +2026-01-09 13:40:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:40:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:40:55 +2026-01-09 13:40:55 - 数据库插入成功 +2026-01-09 13:40:55 - 上传线程结束: /mnt/save/warning/alarm_20260109_134054.jpg: 成功! +2026-01-09 13:40:59 - 开始上传图片: /mnt/save/warning/alarm_20260109_134058.jpg +2026-01-09 13:40:59 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:40:59 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:40:59 +2026-01-09 13:41:00 - 数据库插入成功 +2026-01-09 13:41:00 - 上传线程结束: /mnt/save/warning/alarm_20260109_134058.jpg: 成功! +2026-01-09 13:41:03 - 开始上传图片: /mnt/save/warning/alarm_20260109_134102.jpg +2026-01-09 13:41:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:03 +2026-01-09 13:41:03 - 数据库插入成功 +2026-01-09 13:41:03 - 上传线程结束: /mnt/save/warning/alarm_20260109_134102.jpg: 成功! +2026-01-09 13:41:07 - 开始上传图片: /mnt/save/warning/alarm_20260109_134106.jpg +2026-01-09 13:41:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:07 +2026-01-09 13:41:07 - 数据库插入成功 +2026-01-09 13:41:07 - 上传线程结束: /mnt/save/warning/alarm_20260109_134106.jpg: 成功! +2026-01-09 13:41:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:41:19 - 开始上传图片: /mnt/save/warning/alarm_20260109_134118.jpg +2026-01-09 13:41:19 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:19 +2026-01-09 13:41:19 - 数据库插入成功 +2026-01-09 13:41:19 - 上传线程结束: /mnt/save/warning/alarm_20260109_134118.jpg: 成功! +2026-01-09 13:41:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_134127.jpg +2026-01-09 13:41:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:29 +2026-01-09 13:41:29 - 数据库插入成功 +2026-01-09 13:41:29 - 上传线程结束: /mnt/save/warning/alarm_20260109_134127.jpg: 成功! +2026-01-09 13:41:35 - 开始上传图片: /mnt/save/warning/alarm_20260109_134133.jpg +2026-01-09 13:41:35 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:35 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:35 +2026-01-09 13:41:35 - 数据库插入成功 +2026-01-09 13:41:35 - 上传线程结束: /mnt/save/warning/alarm_20260109_134133.jpg: 成功! +2026-01-09 13:41:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_134139.jpg +2026-01-09 13:41:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:39 +2026-01-09 13:41:39 - 数据库插入成功 +2026-01-09 13:41:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_134139.jpg: 成功! +2026-01-09 13:41:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:41:43 - 开始上传图片: /mnt/save/warning/alarm_20260109_134143.jpg +2026-01-09 13:41:43 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:43 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:43 +2026-01-09 13:41:43 - 数据库插入成功 +2026-01-09 13:41:43 - 上传线程结束: /mnt/save/warning/alarm_20260109_134143.jpg: 成功! +2026-01-09 13:41:47 - 开始上传图片: /mnt/save/warning/alarm_20260109_134146.jpg +2026-01-09 13:41:47 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:47 +2026-01-09 13:41:47 - 数据库插入成功 +2026-01-09 13:41:47 - 上传线程结束: /mnt/save/warning/alarm_20260109_134146.jpg: 成功! +2026-01-09 13:41:51 - 开始上传图片: /mnt/save/warning/alarm_20260109_134151.jpg +2026-01-09 13:41:51 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:51 +2026-01-09 13:41:51 - 数据库插入成功 +2026-01-09 13:41:51 - 上传线程结束: /mnt/save/warning/alarm_20260109_134151.jpg: 成功! +2026-01-09 13:41:55 - 开始上传图片: /mnt/save/warning/alarm_20260109_134154.jpg +2026-01-09 13:41:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:55 +2026-01-09 13:41:55 - 数据库插入成功 +2026-01-09 13:41:55 - 上传线程结束: /mnt/save/warning/alarm_20260109_134154.jpg: 成功! +2026-01-09 13:41:57 - 开始上传图片: /mnt/save/warning/alarm_20260109_134156.jpg +2026-01-09 13:41:57 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:41:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:41:57 +2026-01-09 13:41:57 - 数据库插入成功 +2026-01-09 13:41:57 - 上传线程结束: /mnt/save/warning/alarm_20260109_134156.jpg: 成功! +2026-01-09 13:42:03 - 开始上传图片: /mnt/save/warning/alarm_20260109_134202.jpg +2026-01-09 13:42:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:42:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:42:03 +2026-01-09 13:42:03 - 数据库插入成功 +2026-01-09 13:42:03 - 上传线程结束: /mnt/save/warning/alarm_20260109_134202.jpg: 成功! +2026-01-09 13:42:07 - 开始上传图片: /mnt/save/warning/alarm_20260109_134207.jpg +2026-01-09 13:42:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:42:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:42:07 +2026-01-09 13:42:07 - 数据库插入成功 +2026-01-09 13:42:07 - 上传线程结束: /mnt/save/warning/alarm_20260109_134207.jpg: 成功! +2026-01-09 13:42:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:42:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:42:55 - 开始上传图片: /mnt/save/warning/alarm_20260109_134255.jpg +2026-01-09 13:42:55 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:42:55 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:42:55 +2026-01-09 13:42:56 - 数据库插入成功 +2026-01-09 13:42:56 - 上传线程结束: /mnt/save/warning/alarm_20260109_134255.jpg: 成功! +2026-01-09 13:43:04 - 开始上传图片: /mnt/save/warning/alarm_20260109_134302.jpg +2026-01-09 13:43:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:43:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:43:04 +2026-01-09 13:43:04 - 数据库插入成功 +2026-01-09 13:43:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_134302.jpg: 成功! +2026-01-09 13:43:06 - 开始上传图片: /mnt/save/warning/alarm_20260109_134304.jpg +2026-01-09 13:43:06 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:43:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:43:06 +2026-01-09 13:43:08 - 数据库插入成功 +2026-01-09 13:43:08 - 上传线程结束: /mnt/save/warning/alarm_20260109_134304.jpg: 成功! +2026-01-09 13:43:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:43:28 - 开始上传图片: /mnt/save/warning/alarm_20260109_134328.jpg +2026-01-09 13:43:28 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:43:28 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:43:28 +2026-01-09 13:43:28 - 数据库插入成功 +2026-01-09 13:43:28 - 上传线程结束: /mnt/save/warning/alarm_20260109_134328.jpg: 成功! +2026-01-09 13:43:40 - 开始上传图片: /mnt/save/warning/alarm_20260109_134338.jpg +2026-01-09 13:43:40 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:43:40 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:43:40 +2026-01-09 13:43:40 - 数据库插入成功 +2026-01-09 13:43:40 - 上传线程结束: /mnt/save/warning/alarm_20260109_134338.jpg: 成功! +2026-01-09 13:43:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:43:44 - 开始上传图片: /mnt/save/warning/alarm_20260109_134343.jpg +2026-01-09 13:43:44 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:43:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:43:44 +2026-01-09 13:43:44 - 数据库插入成功 +2026-01-09 13:43:44 - 上传线程结束: /mnt/save/warning/alarm_20260109_134343.jpg: 成功! +2026-01-09 13:43:54 - 开始上传图片: /mnt/save/warning/alarm_20260109_134354.jpg +2026-01-09 13:43:54 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:43:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:43:54 +2026-01-09 13:43:54 - 数据库插入成功 +2026-01-09 13:43:54 - 上传线程结束: /mnt/save/warning/alarm_20260109_134354.jpg: 成功! +2026-01-09 13:44:02 - 开始上传图片: /mnt/save/warning/alarm_20260109_134400.jpg +2026-01-09 13:44:02 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:44:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:44:02 +2026-01-09 13:44:02 - 数据库插入成功 +2026-01-09 13:44:02 - 上传线程结束: /mnt/save/warning/alarm_20260109_134400.jpg: 成功! +2026-01-09 13:44:06 - 开始上传图片: /mnt/save/warning/alarm_20260109_134405.jpg +2026-01-09 13:44:06 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:44:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:44:06 +2026-01-09 13:44:06 - 数据库插入成功 +2026-01-09 13:44:06 - 上传线程结束: /mnt/save/warning/alarm_20260109_134405.jpg: 成功! +2026-01-09 13:44:08 - 开始上传图片: /mnt/save/warning/alarm_20260109_134407.jpg +2026-01-09 13:44:08 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:44:08 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:44:08 +2026-01-09 13:44:09 - 数据库插入成功 +2026-01-09 13:44:09 - 上传线程结束: /mnt/save/warning/alarm_20260109_134407.jpg: 成功! +2026-01-09 13:44:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:44:20 - 开始上传图片: /mnt/save/warning/alarm_20260109_134420.jpg +2026-01-09 13:44:20 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:44:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:44:20 +2026-01-09 13:44:20 - 数据库插入成功 +2026-01-09 13:44:20 - 上传线程结束: /mnt/save/warning/alarm_20260109_134420.jpg: 成功! +2026-01-09 13:44:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:45:05 - 开始上传图片: /mnt/save/warning/alarm_20260109_134504.jpg +2026-01-09 13:45:05 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:45:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:45:05 +2026-01-09 13:45:05 - 数据库插入成功 +2026-01-09 13:45:05 - 上传线程结束: /mnt/save/warning/alarm_20260109_134504.jpg: 成功! +2026-01-09 13:45:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:45:25 - 开始上传图片: /mnt/save/warning/alarm_20260109_134523.jpg +2026-01-09 13:45:25 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:45:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:45:25 +2026-01-09 13:45:25 - 数据库插入成功 +2026-01-09 13:45:25 - 上传线程结束: /mnt/save/warning/alarm_20260109_134523.jpg: 成功! +2026-01-09 13:45:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_134538.jpg +2026-01-09 13:45:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:45:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:45:39 +2026-01-09 13:45:39 - 数据库插入成功 +2026-01-09 13:45:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_134538.jpg: 成功! +2026-01-09 13:45:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:45:47 - 开始上传图片: /mnt/save/warning/alarm_20260109_134546.jpg +2026-01-09 13:45:47 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:45:47 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:45:47 +2026-01-09 13:45:47 - 数据库插入成功 +2026-01-09 13:45:47 - 上传线程结束: /mnt/save/warning/alarm_20260109_134546.jpg: 成功! +2026-01-09 13:46:07 - 开始上传图片: /mnt/save/warning/alarm_20260109_134606.jpg +2026-01-09 13:46:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:46:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:46:07 +2026-01-09 13:46:08 - 数据库插入成功 +2026-01-09 13:46:08 - 上传线程结束: /mnt/save/warning/alarm_20260109_134606.jpg: 成功! +2026-01-09 13:46:11 - 开始上传图片: /mnt/save/warning/alarm_20260109_134610.jpg +2026-01-09 13:46:11 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:46:11 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:46:11 +2026-01-09 13:46:12 - 数据库插入成功 +2026-01-09 13:46:12 - 上传线程结束: /mnt/save/warning/alarm_20260109_134610.jpg: 成功! +2026-01-09 13:46:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:46:15 - 开始上传图片: /mnt/save/warning/alarm_20260109_134614.jpg +2026-01-09 13:46:15 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:46:15 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:46:15 +2026-01-09 13:46:16 - 数据库插入成功 +2026-01-09 13:46:16 - 上传线程结束: /mnt/save/warning/alarm_20260109_134614.jpg: 成功! +2026-01-09 13:46:23 - 开始上传图片: /mnt/save/warning/alarm_20260109_134622.jpg +2026-01-09 13:46:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:46:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:46:24 +2026-01-09 13:46:24 - 数据库插入成功 +2026-01-09 13:46:24 - 上传线程结束: /mnt/save/warning/alarm_20260109_134622.jpg: 成功! +2026-01-09 13:46:32 - 开始上传图片: /mnt/save/warning/alarm_20260109_134630.jpg +2026-01-09 13:46:32 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:46:32 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:46:32 +2026-01-09 13:46:32 - 数据库插入成功 +2026-01-09 13:46:32 - 上传线程结束: /mnt/save/warning/alarm_20260109_134630.jpg: 成功! +2026-01-09 13:46:42 - 开始上传图片: /mnt/save/warning/alarm_20260109_134641.jpg +2026-01-09 13:46:42 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:46:42 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:46:42 +2026-01-09 13:46:42 - 数据库插入成功 +2026-01-09 13:46:42 - 上传线程结束: /mnt/save/warning/alarm_20260109_134641.jpg: 成功! +2026-01-09 13:46:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:47:02 - 开始上传图片: /mnt/save/warning/alarm_20260109_134701.jpg +2026-01-09 13:47:02 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:47:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:47:02 +2026-01-09 13:47:02 - 数据库插入成功 +2026-01-09 13:47:02 - 上传线程结束: /mnt/save/warning/alarm_20260109_134701.jpg: 成功! +2026-01-09 13:47:04 - 开始上传图片: /mnt/save/warning/alarm_20260109_134703.jpg +2026-01-09 13:47:04 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:47:04 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:47:04 +2026-01-09 13:47:04 - 数据库插入成功 +2026-01-09 13:47:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_134703.jpg: 成功! +2026-01-09 13:47:10 - 开始上传图片: /mnt/save/warning/alarm_20260109_134710.jpg +2026-01-09 13:47:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:47:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:47:10 +2026-01-09 13:47:10 - 数据库插入成功 +2026-01-09 13:47:10 - 上传线程结束: /mnt/save/warning/alarm_20260109_134710.jpg: 成功! +2026-01-09 13:47:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:47:14 - 开始上传图片: /mnt/save/warning/alarm_20260109_134712.jpg +2026-01-09 13:47:14 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:47:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:47:14 +2026-01-09 13:47:14 - 数据库插入成功 +2026-01-09 13:47:14 - 上传线程结束: /mnt/save/warning/alarm_20260109_134712.jpg: 成功! +2026-01-09 13:47:18 - 开始上传图片: /mnt/save/warning/alarm_20260109_134717.jpg +2026-01-09 13:47:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:47:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:47:18 +2026-01-09 13:47:18 - 数据库插入成功 +2026-01-09 13:47:18 - 上传线程结束: /mnt/save/warning/alarm_20260109_134717.jpg: 成功! +2026-01-09 13:47:26 - 开始上传图片: /mnt/save/warning/alarm_20260109_134726.jpg +2026-01-09 13:47:26 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:47:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:47:26 +2026-01-09 13:47:26 - 数据库插入成功 +2026-01-09 13:47:26 - 上传线程结束: /mnt/save/warning/alarm_20260109_134726.jpg: 成功! +2026-01-09 13:47:30 - 开始上传图片: /mnt/save/warning/alarm_20260109_134729.jpg +2026-01-09 13:47:30 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:47:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:47:30 +2026-01-09 13:47:30 - 数据库插入成功 +2026-01-09 13:47:30 - 上传线程结束: /mnt/save/warning/alarm_20260109_134729.jpg: 成功! +2026-01-09 13:47:36 - 开始上传图片: /mnt/save/warning/alarm_20260109_134735.jpg +2026-01-09 13:47:36 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:47:36 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:47:36 +2026-01-09 13:47:36 - 数据库插入成功 +2026-01-09 13:47:36 - 上传线程结束: /mnt/save/warning/alarm_20260109_134735.jpg: 成功! +2026-01-09 13:47:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:48:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:48:29 - 开始上传图片: /mnt/save/warning/alarm_20260109_134827.jpg +2026-01-09 13:48:29 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:48:29 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:48:29 +2026-01-09 13:48:29 - 数据库插入成功 +2026-01-09 13:48:29 - 上传线程结束: /mnt/save/warning/alarm_20260109_134827.jpg: 成功! +2026-01-09 13:48:33 - 开始上传图片: /mnt/save/warning/alarm_20260109_134832.jpg +2026-01-09 13:48:33 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:48:33 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:48:33 +2026-01-09 13:48:33 - 数据库插入成功 +2026-01-09 13:48:33 - 上传线程结束: /mnt/save/warning/alarm_20260109_134832.jpg: 成功! +2026-01-09 13:48:37 - 开始上传图片: /mnt/save/warning/alarm_20260109_134836.jpg +2026-01-09 13:48:37 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:48:37 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:48:37 +2026-01-09 13:48:37 - 数据库插入成功 +2026-01-09 13:48:37 - 上传线程结束: /mnt/save/warning/alarm_20260109_134836.jpg: 成功! +2026-01-09 13:48:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:48:49 - 开始上传图片: /mnt/save/warning/alarm_20260109_134847.jpg +2026-01-09 13:48:49 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:48:49 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:48:49 +2026-01-09 13:48:49 - 数据库插入成功 +2026-01-09 13:48:49 - 上传线程结束: /mnt/save/warning/alarm_20260109_134847.jpg: 成功! +2026-01-09 13:49:05 - 开始上传图片: /mnt/save/warning/alarm_20260109_134904.jpg +2026-01-09 13:49:05 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:49:05 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:49:05 +2026-01-09 13:49:05 - 数据库插入成功 +2026-01-09 13:49:05 - 上传线程结束: /mnt/save/warning/alarm_20260109_134904.jpg: 成功! +2026-01-09 13:49:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:49:13 - 开始上传图片: /mnt/save/warning/alarm_20260109_134911.jpg +2026-01-09 13:49:13 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:49:13 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:49:13 +2026-01-09 13:49:14 - 数据库插入成功 +2026-01-09 13:49:14 - 上传线程结束: /mnt/save/warning/alarm_20260109_134911.jpg: 成功! +2026-01-09 13:49:38 - 开始上传图片: /mnt/save/warning/alarm_20260109_134937.jpg +2026-01-09 13:49:38 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:49:38 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:49:38 +2026-01-09 13:49:38 - 数据库插入成功 +2026-01-09 13:49:38 - 上传线程结束: /mnt/save/warning/alarm_20260109_134937.jpg: 成功! +2026-01-09 13:49:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:49:56 - 开始上传图片: /mnt/save/warning/alarm_20260109_134955.jpg +2026-01-09 13:49:56 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:49:56 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:49:56 +2026-01-09 13:49:56 - 数据库插入成功 +2026-01-09 13:49:56 - 上传线程结束: /mnt/save/warning/alarm_20260109_134955.jpg: 成功! +2026-01-09 13:50:06 - 开始上传图片: /mnt/save/warning/alarm_20260109_135005.jpg +2026-01-09 13:50:06 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:06 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:06 +2026-01-09 13:50:06 - 数据库插入成功 +2026-01-09 13:50:06 - 上传线程结束: /mnt/save/warning/alarm_20260109_135005.jpg: 成功! +2026-01-09 13:50:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:50:14 - 开始上传图片: /mnt/save/warning/alarm_20260109_135012.jpg +2026-01-09 13:50:14 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:14 +2026-01-09 13:50:14 - 数据库插入成功 +2026-01-09 13:50:14 - 上传线程结束: /mnt/save/warning/alarm_20260109_135012.jpg: 成功! +2026-01-09 13:50:16 - 开始上传图片: /mnt/save/warning/alarm_20260109_135015.jpg +2026-01-09 13:50:16 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:16 +2026-01-09 13:50:16 - 数据库插入成功 +2026-01-09 13:50:16 - 上传线程结束: /mnt/save/warning/alarm_20260109_135015.jpg: 成功! +2026-01-09 13:50:22 - 开始上传图片: /mnt/save/warning/alarm_20260109_135020.jpg +2026-01-09 13:50:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:22 +2026-01-09 13:50:22 - 数据库插入成功 +2026-01-09 13:50:22 - 上传线程结束: /mnt/save/warning/alarm_20260109_135020.jpg: 成功! +2026-01-09 13:50:24 - 开始上传图片: /mnt/save/warning/alarm_20260109_135024.jpg +2026-01-09 13:50:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:24 +2026-01-09 13:50:24 - 数据库插入成功 +2026-01-09 13:50:24 - 上传线程结束: /mnt/save/warning/alarm_20260109_135024.jpg: 成功! +2026-01-09 13:50:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:50:44 - 开始上传图片: /mnt/save/warning/alarm_20260109_135043.jpg +2026-01-09 13:50:44 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:44 +2026-01-09 13:50:44 - 数据库插入成功 +2026-01-09 13:50:44 - 上传线程结束: /mnt/save/warning/alarm_20260109_135043.jpg: 成功! +2026-01-09 13:50:48 - 开始上传图片: /mnt/save/warning/alarm_20260109_135047.jpg +2026-01-09 13:50:48 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:48 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:48 +2026-01-09 13:50:48 - 数据库插入成功 +2026-01-09 13:50:48 - 上传线程结束: /mnt/save/warning/alarm_20260109_135047.jpg: 成功! +2026-01-09 13:50:50 - 开始上传图片: /mnt/save/warning/alarm_20260109_135050.jpg +2026-01-09 13:50:50 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:50 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:50 +2026-01-09 13:50:50 - 数据库插入成功 +2026-01-09 13:50:50 - 上传线程结束: /mnt/save/warning/alarm_20260109_135050.jpg: 成功! +2026-01-09 13:50:54 - 开始上传图片: /mnt/save/warning/alarm_20260109_135054.jpg +2026-01-09 13:50:54 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:54 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:54 +2026-01-09 13:50:54 - 数据库插入成功 +2026-01-09 13:50:54 - 上传线程结束: /mnt/save/warning/alarm_20260109_135054.jpg: 成功! +2026-01-09 13:50:58 - 开始上传图片: /mnt/save/warning/alarm_20260109_135058.jpg +2026-01-09 13:50:58 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:50:58 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:50:58 +2026-01-09 13:50:59 - 数据库插入成功 +2026-01-09 13:50:59 - 上传线程结束: /mnt/save/warning/alarm_20260109_135058.jpg: 成功! +2026-01-09 13:51:02 - 开始上传图片: /mnt/save/warning/alarm_20260109_135101.jpg +2026-01-09 13:51:02 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:51:02 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:51:02 +2026-01-09 13:51:03 - 数据库插入成功 +2026-01-09 13:51:03 - 上传线程结束: /mnt/save/warning/alarm_20260109_135101.jpg: 成功! +2026-01-09 13:51:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:51:24 - 开始上传图片: /mnt/save/warning/alarm_20260109_135124.jpg +2026-01-09 13:51:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:51:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:51:24 +2026-01-09 13:51:25 - 数据库插入成功 +2026-01-09 13:51:25 - 上传线程结束: /mnt/save/warning/alarm_20260109_135124.jpg: 成功! +2026-01-09 13:51:26 - 开始上传图片: /mnt/save/warning/alarm_20260109_135126.jpg +2026-01-09 13:51:26 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:51:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:51:26 +2026-01-09 13:51:27 - 数据库插入成功 +2026-01-09 13:51:27 - 上传线程结束: /mnt/save/warning/alarm_20260109_135126.jpg: 成功! +2026-01-09 13:51:34 - 开始上传图片: /mnt/save/warning/alarm_20260109_135133.jpg +2026-01-09 13:51:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:51:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:51:34 +2026-01-09 13:51:35 - 数据库插入成功 +2026-01-09 13:51:35 - 上传线程结束: /mnt/save/warning/alarm_20260109_135133.jpg: 成功! +2026-01-09 13:51:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:51:45 - 开始上传图片: /mnt/save/warning/alarm_20260109_135143.jpg +2026-01-09 13:51:45 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:51:45 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:51:45 +2026-01-09 13:51:45 - 数据库插入成功 +2026-01-09 13:51:45 - 上传线程结束: /mnt/save/warning/alarm_20260109_135143.jpg: 成功! +2026-01-09 13:52:01 - 开始上传图片: /mnt/save/warning/alarm_20260109_135159.jpg +2026-01-09 13:52:01 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:52:01 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:52:01 +2026-01-09 13:52:01 - 数据库插入成功 +2026-01-09 13:52:01 - 上传线程结束: /mnt/save/warning/alarm_20260109_135159.jpg: 成功! +2026-01-09 13:52:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:52:17 - 开始上传图片: /mnt/save/warning/alarm_20260109_135216.jpg +2026-01-09 13:52:17 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:52:17 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:52:17 +2026-01-09 13:52:17 - 数据库插入成功 +2026-01-09 13:52:17 - 上传线程结束: /mnt/save/warning/alarm_20260109_135216.jpg: 成功! +2026-01-09 13:52:25 - 开始上传图片: /mnt/save/warning/alarm_20260109_135223.jpg +2026-01-09 13:52:25 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:52:25 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:52:25 +2026-01-09 13:52:25 - 数据库插入成功 +2026-01-09 13:52:25 - 上传线程结束: /mnt/save/warning/alarm_20260109_135223.jpg: 成功! +2026-01-09 13:52:27 - 开始上传图片: /mnt/save/warning/alarm_20260109_135226.jpg +2026-01-09 13:52:27 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:52:27 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:52:27 +2026-01-09 13:52:27 - 数据库插入成功 +2026-01-09 13:52:27 - 上传线程结束: /mnt/save/warning/alarm_20260109_135226.jpg: 成功! +2026-01-09 13:52:39 - 开始上传图片: /mnt/save/warning/alarm_20260109_135237.jpg +2026-01-09 13:52:39 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:52:39 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:52:39 +2026-01-09 13:52:39 - 数据库插入成功 +2026-01-09 13:52:39 - 上传线程结束: /mnt/save/warning/alarm_20260109_135237.jpg: 成功! +2026-01-09 13:52:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:52:51 - 开始上传图片: /mnt/save/warning/alarm_20260109_135250.jpg +2026-01-09 13:52:51 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:52:51 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:52:51 +2026-01-09 13:52:51 - 数据库插入成功 +2026-01-09 13:52:51 - 上传线程结束: /mnt/save/warning/alarm_20260109_135250.jpg: 成功! +2026-01-09 13:52:57 - 开始上传图片: /mnt/save/warning/alarm_20260109_135256.jpg +2026-01-09 13:52:57 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:52:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:52:57 +2026-01-09 13:52:57 - 数据库插入成功 +2026-01-09 13:52:57 - 上传线程结束: /mnt/save/warning/alarm_20260109_135256.jpg: 成功! +2026-01-09 13:53:03 - 开始上传图片: /mnt/save/warning/alarm_20260109_135301.jpg +2026-01-09 13:53:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:53:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:53:03 +2026-01-09 13:53:03 - 数据库插入成功 +2026-01-09 13:53:03 - 上传线程结束: /mnt/save/warning/alarm_20260109_135301.jpg: 成功! +2026-01-09 13:53:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:53:41 - 开始上传图片: /mnt/save/warning/alarm_20260109_135341.jpg +2026-01-09 13:53:41 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:53:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:53:41 +2026-01-09 13:53:42 - 数据库插入成功 +2026-01-09 13:53:42 - 上传线程结束: /mnt/save/warning/alarm_20260109_135341.jpg: 成功! +2026-01-09 13:53:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:54:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:54:43 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:55:10 - 开始上传图片: /mnt/save/warning/alarm_20260109_135510.jpg +2026-01-09 13:55:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:55:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:55:10 +2026-01-09 13:55:11 - 数据库插入成功 +2026-01-09 13:55:11 - 上传线程结束: /mnt/save/warning/alarm_20260109_135510.jpg: 成功! +2026-01-09 13:55:13 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:55:16 - 开始上传图片: /mnt/save/warning/alarm_20260109_135516.jpg +2026-01-09 13:55:16 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:55:16 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:55:16 +2026-01-09 13:55:17 - 数据库插入成功 +2026-01-09 13:55:17 - 上传线程结束: /mnt/save/warning/alarm_20260109_135516.jpg: 成功! +2026-01-09 13:55:18 - 开始上传图片: /mnt/save/warning/alarm_20260109_135518.jpg +2026-01-09 13:55:18 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:55:18 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:55:18 +2026-01-09 13:55:19 - 数据库插入成功 +2026-01-09 13:55:19 - 上传线程结束: /mnt/save/warning/alarm_20260109_135518.jpg: 成功! +2026-01-09 13:55:22 - 开始上传图片: /mnt/save/warning/alarm_20260109_135520.jpg +2026-01-09 13:55:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:55:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:55:22 +2026-01-09 13:55:23 - 数据库插入成功 +2026-01-09 13:55:23 - 上传线程结束: /mnt/save/warning/alarm_20260109_135520.jpg: 成功! +2026-01-09 13:55:24 - 开始上传图片: /mnt/save/warning/alarm_20260109_135523.jpg +2026-01-09 13:55:24 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:55:24 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:55:24 +2026-01-09 13:55:25 - 数据库插入成功 +2026-01-09 13:55:25 - 上传线程结束: /mnt/save/warning/alarm_20260109_135523.jpg: 成功! +2026-01-09 13:55:26 - 开始上传图片: /mnt/save/warning/alarm_20260109_135526.jpg +2026-01-09 13:55:26 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:55:26 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:55:26 +2026-01-09 13:55:27 - 数据库插入成功 +2026-01-09 13:55:27 - 上传线程结束: /mnt/save/warning/alarm_20260109_135526.jpg: 成功! +2026-01-09 13:55:41 - 开始上传图片: /mnt/save/warning/alarm_20260109_135539.jpg +2026-01-09 13:55:41 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:55:41 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:55:41 +2026-01-09 13:55:41 - 数据库插入成功 +2026-01-09 13:55:41 - 上传线程结束: /mnt/save/warning/alarm_20260109_135539.jpg: 成功! +2026-01-09 13:55:44 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:55:53 - 开始上传图片: /mnt/save/warning/alarm_20260109_135551.jpg +2026-01-09 13:55:53 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:55:53 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:55:53 +2026-01-09 13:55:53 - 数据库插入成功 +2026-01-09 13:55:53 - 上传线程结束: /mnt/save/warning/alarm_20260109_135551.jpg: 成功! +2026-01-09 13:55:57 - 开始上传图片: /mnt/save/warning/alarm_20260109_135555.jpg +2026-01-09 13:55:57 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:55:57 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:55:57 +2026-01-09 13:55:57 - 数据库插入成功 +2026-01-09 13:55:57 - 上传线程结束: /mnt/save/warning/alarm_20260109_135555.jpg: 成功! +2026-01-09 13:56:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_135607.jpg +2026-01-09 13:56:09 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:56:09 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:56:09 +2026-01-09 13:56:09 - 数据库插入成功 +2026-01-09 13:56:09 - 上传线程结束: /mnt/save/warning/alarm_20260109_135607.jpg: 成功! +2026-01-09 13:56:14 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:56:19 - 开始上传图片: /mnt/save/warning/alarm_20260109_135618.jpg +2026-01-09 13:56:19 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:56:19 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:56:19 +2026-01-09 13:56:19 - 数据库插入成功 +2026-01-09 13:56:19 - 上传线程结束: /mnt/save/warning/alarm_20260109_135618.jpg: 成功! +2026-01-09 13:56:44 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:57:03 - 开始上传图片: /mnt/save/warning/alarm_20260109_135702.jpg +2026-01-09 13:57:03 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:57:03 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:57:03 +2026-01-09 13:57:04 - 数据库插入成功 +2026-01-09 13:57:04 - 上传线程结束: /mnt/save/warning/alarm_20260109_135702.jpg: 成功! +2026-01-09 13:57:07 - 开始上传图片: /mnt/save/warning/alarm_20260109_135706.jpg +2026-01-09 13:57:07 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:57:07 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:57:07 +2026-01-09 13:57:08 - 数据库插入成功 +2026-01-09 13:57:08 - 上传线程结束: /mnt/save/warning/alarm_20260109_135706.jpg: 成功! +2026-01-09 13:57:09 - 开始上传图片: /mnt/save/warning/alarm_20260109_135709.jpg +2026-01-09 13:57:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:57:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:57:10 +2026-01-09 13:57:10 - 数据库插入成功 +2026-01-09 13:57:10 - 上传线程结束: /mnt/save/warning/alarm_20260109_135709.jpg: 成功! +2026-01-09 13:57:14 - 开始上传图片: /mnt/save/warning/alarm_20260109_135712.jpg +2026-01-09 13:57:14 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:57:14 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:57:14 +2026-01-09 13:57:14 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:57:14 - 数据库插入成功 +2026-01-09 13:57:14 - 上传线程结束: /mnt/save/warning/alarm_20260109_135712.jpg: 成功! +2026-01-09 13:57:20 - 开始上传图片: /mnt/save/warning/alarm_20260109_135720.jpg +2026-01-09 13:57:20 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:57:20 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:57:20 +2026-01-09 13:57:20 - 数据库插入成功 +2026-01-09 13:57:20 - 上传线程结束: /mnt/save/warning/alarm_20260109_135720.jpg: 成功! +2026-01-09 13:57:30 - 开始上传图片: /mnt/save/warning/alarm_20260109_135728.jpg +2026-01-09 13:57:30 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:57:30 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:57:30 +2026-01-09 13:57:30 - 数据库插入成功 +2026-01-09 13:57:30 - 上传线程结束: /mnt/save/warning/alarm_20260109_135728.jpg: 成功! +2026-01-09 13:57:34 - 开始上传图片: /mnt/save/warning/alarm_20260109_135732.jpg +2026-01-09 13:57:34 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:57:34 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:57:34 +2026-01-09 13:57:34 - 数据库插入成功 +2026-01-09 13:57:34 - 上传线程结束: /mnt/save/warning/alarm_20260109_135732.jpg: 成功! +2026-01-09 13:57:44 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:57:44 - 开始上传图片: /mnt/save/warning/alarm_20260109_135742.jpg +2026-01-09 13:57:44 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:57:44 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:57:44 +2026-01-09 13:57:44 - 数据库插入成功 +2026-01-09 13:57:44 - 上传线程结束: /mnt/save/warning/alarm_20260109_135742.jpg: 成功! +2026-01-09 13:58:10 - 开始上传图片: /mnt/save/warning/alarm_20260109_135809.jpg +2026-01-09 13:58:10 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:58:10 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:58:10 +2026-01-09 13:58:10 - 数据库插入成功 +2026-01-09 13:58:10 - 上传线程结束: /mnt/save/warning/alarm_20260109_135809.jpg: 成功! +2026-01-09 13:58:14 - 读取ICCID异常: (5, 'Input/output error') +2026-01-09 13:58:22 - 开始上传图片: /mnt/save/warning/alarm_20260109_135822.jpg +2026-01-09 13:58:22 - 串口读取失败: device reports readiness to read but returned no data (device disconnected or multiple access on port?) +2026-01-09 13:58:22 - 准备入库: lon=1201, lat=3129, card_no=898604581824D0366321, create_time=2026-01-09 13:58:22 +2026-01-09 13:58:22 - 数据库插入成功 +2026-01-09 13:58:22 - 上传线程结束: /mnt/save/warning/alarm_20260109_135822.jpg: 成功! diff --git a/RTSPServer/LICENSE b/RTSPServer/LICENSE new file mode 100644 index 0000000..50bbbd8 --- /dev/null +++ b/RTSPServer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 aler9 + +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 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. diff --git a/RTSPServer/mediamtx b/RTSPServer/mediamtx new file mode 100755 index 0000000..3943bf9 Binary files /dev/null and b/RTSPServer/mediamtx differ diff --git a/RTSPServer/mediamtx.yml b/RTSPServer/mediamtx.yml new file mode 100644 index 0000000..b5b33c6 --- /dev/null +++ b/RTSPServer/mediamtx.yml @@ -0,0 +1,739 @@ +############################################### +# Global settings + +# Settings in this section are applied anywhere. + +############################################### +# Global settings -> General + +# Verbosity of the program; available values are "error", "warn", "info", "debug". +logLevel: info +# Destinations of log messages; available values are "stdout", "file" and "syslog". +logDestinations: [stdout] +# If "file" is in logDestinations, this is the file which will receive the logs. +logFile: mediamtx.log +# If "syslog" is in logDestinations, use prefix for logs. +sysLogPrefix: mediamtx + +# Timeout of read operations. +readTimeout: 10s +# Timeout of write operations. +writeTimeout: 10s +# Size of the queue of outgoing packets. +# A higher value allows to increase throughput, a lower value allows to save RAM. +writeQueueSize: 512 +# Maximum size of outgoing UDP packets. +# This can be decreased to avoid fragmentation on networks with a low UDP MTU. +udpMaxPayloadSize: 1472 + +# Command to run when a client connects to the server. +# This is terminated with SIGINT when a client disconnects from the server. +# The following environment variables are available: +# * MTX_CONN_TYPE: connection type +# * MTX_CONN_ID: connection ID +# * RTSP_PORT: RTSP server port +runOnConnect: +# Restart the command if it exits. +runOnConnectRestart: no +# Command to run when a client disconnects from the server. +# Environment variables are the same of runOnConnect. +runOnDisconnect: + +############################################### +# Global settings -> Authentication + +# Authentication method. Available values are: +# * internal: users are stored in the configuration file +# * http: an external HTTP URL is contacted to perform authentication +# * jwt: an external identity server provides authentication through JWTs +authMethod: internal + +# Internal authentication. +# list of users. +authInternalUsers: + # Default unprivileged user. + # Username. 'any' means any user, including anonymous ones. +- user: any + # Password. Not used in case of 'any' user. + pass: + # IPs or networks allowed to use this user. An empty list means any IP. + ips: [] + # List of permissions. + permissions: + # Available actions are: publish, read, playback, api, metrics, pprof. + - action: publish + # Paths can be set to further restrict access to a specific path. + # An empty path means any path. + # Regular expressions can be used by using a tilde as prefix. + path: + - action: read + path: + - action: playback + path: + + # Default administrator. + # This allows to use API, metrics and PPROF without authentication, + # if the IP is localhost. +- user: any + pass: + ips: ['127.0.0.1', '::1'] + permissions: + - action: api + - action: metrics + - action: pprof + +# HTTP-based authentication. +# URL called to perform authentication. Every time a user wants +# to authenticate, the server calls this URL with the POST method +# and a body containing: +# { +# "user": "user", +# "password": "password", +# "token": "token", +# "ip": "ip", +# "action": "publish|read|playback|api|metrics|pprof", +# "path": "path", +# "protocol": "rtsp|rtmp|hls|webrtc|srt", +# "id": "id", +# "query": "query" +# } +# If the response code is 20x, authentication is accepted, otherwise +# it is discarded. +authHTTPAddress: +# Actions to exclude from HTTP-based authentication. +# Format is the same as the one of user permissions. +authHTTPExclude: +- action: api +- action: metrics +- action: pprof + +# JWT-based authentication. +# Users have to login through an external identity server and obtain a JWT. +# This JWT must contain the claim "mediamtx_permissions" with permissions, +# for instance: +# { +# "mediamtx_permissions": [ +# { +# "action": "publish", +# "path": "somepath" +# } +# ] +# } +# Users are expected to pass the JWT in the Authorization header, password or query parameter. +# This is the JWKS URL that will be used to pull (once) the public key that allows +# to validate JWTs. +authJWTJWKS: +# If the JWKS URL has a self-signed or invalid certificate, +# you can provide the fingerprint of the certificate in order to +# validate it anyway. It can be obtained by running: +# openssl s_client -connect jwt_jwks_domain:443 /dev/null | sed -n '/BEGIN/,/END/p' > server.crt +# openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':' +authJWTJWKSFingerprint: +# name of the claim that contains permissions. +authJWTClaimKey: mediamtx_permissions +# Actions to exclude from JWT-based authentication. +# Format is the same as the one of user permissions. +authJWTExclude: [] +# allow passing the JWT through query parameters of HTTP requests (i.e. ?jwt=JWT). +# This is a security risk. +authJWTInHTTPQuery: true + +############################################### +# Global settings -> Control API + +# Enable controlling the server through the Control API. +api: no +# Address of the Control API listener. +apiAddress: :9997 +# Enable TLS/HTTPS on the Control API server. +apiEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +apiServerKey: server.key +# Path to the server certificate. +apiServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +apiAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +apiTrustedProxies: [] + +############################################### +# Global settings -> Metrics + +# Enable Prometheus-compatible metrics. +metrics: no +# Address of the metrics HTTP listener. +metricsAddress: :9998 +# Enable TLS/HTTPS on the Metrics server. +metricsEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +metricsServerKey: server.key +# Path to the server certificate. +metricsServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +metricsAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +metricsTrustedProxies: [] + +############################################### +# Global settings -> PPROF + +# Enable pprof-compatible endpoint to monitor performances. +pprof: no +# Address of the pprof listener. +pprofAddress: :9999 +# Enable TLS/HTTPS on the pprof server. +pprofEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +pprofServerKey: server.key +# Path to the server certificate. +pprofServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +pprofAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +pprofTrustedProxies: [] + +############################################### +# Global settings -> Playback server + +# Enable downloading recordings from the playback server. +playback: no +# Address of the playback server listener. +playbackAddress: :9996 +# Enable TLS/HTTPS on the playback server. +playbackEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +playbackServerKey: server.key +# Path to the server certificate. +playbackServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +playbackAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +playbackTrustedProxies: [] + +############################################### +# Global settings -> RTSP server + +# Enable publishing and reading streams with the RTSP protocol. +rtsp: yes +# List of enabled RTSP transport protocols. +# UDP is the most performant, but doesn't work when there's a NAT/firewall between +# server and clients. +# UDP-multicast allows to save bandwidth when clients are all in the same LAN. +# TCP is the most versatile. +# The handshake is always performed with TCP. +rtspTransports: [udp, multicast, tcp] +# Use secure protocol variants (RTSPS, TLS, SRTP). +# Available values are "no", "strict", "optional". +rtspEncryption: "no" +# Address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional". +rtspAddress: :8554 +# Address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional". +rtspsAddress: :8322 +# Address of the UDP/RTP listener. This is needed only when "udp" is in rtspTransports. +rtpAddress: :8000 +# Address of the UDP/RTCP listener. This is needed only when "udp" is in rtspTransports. +rtcpAddress: :8001 +# IP range of all UDP-multicast listeners. This is needed only when "multicast" is in rtspTransports. +multicastIPRange: 224.1.0.0/16 +# Port of all UDP-multicast/RTP listeners. This is needed only when "multicast" is in rtspTransports. +multicastRTPPort: 8002 +# Port of all UDP-multicast/RTCP listeners. This is needed only when "multicast" is in rtspTransports. +multicastRTCPPort: 8003 +# Address of the UDP/SRTP listener. This is needed only when "udp" is in rtspTransports and encryption is enabled. +srtpAddress: :8004 +# Address of the UDP/SRTCP listener. This is needed only when "udp" is in rtspTransports and encryption is enabled. +srtcpAddress: :8005 +# Port of all UDP-multicast/SRTP listeners. This is needed only when "multicast" is in rtspTransports and encryption is enabled. +multicastSRTPPort: 8006 +# Port of all UDP-multicast/SRTCP listeners. This is needed only when "multicast" is in rtspTransports and encryption is enabled. +multicastSRTCPPort: 8007 +# Path to the server key. This is needed only when encryption is "strict" or "optional". +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +rtspServerKey: server.key +# Path to the server certificate. This is needed only when encryption is "strict" or "optional". +rtspServerCert: server.crt +# Authentication methods. Available are "basic" and "digest". +# "digest" doesn't provide any additional security and is available for compatibility only. +rtspAuthMethods: [basic] + +############################################### +# Global settings -> RTMP server + +# Enable publishing and reading streams with the RTMP protocol. +rtmp: yes +# Address of the RTMP listener. This is needed only when encryption is "no" or "optional". +rtmpAddress: :1935 +# Encrypt connections with TLS (RTMPS). +# Available values are "no", "strict", "optional". +rtmpEncryption: "no" +# Address of the RTMPS listener. This is needed only when encryption is "strict" or "optional". +rtmpsAddress: :1936 +# Path to the server key. This is needed only when encryption is "strict" or "optional". +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +rtmpServerKey: server.key +# Path to the server certificate. This is needed only when encryption is "strict" or "optional". +rtmpServerCert: server.crt + +############################################### +# Global settings -> HLS server + +# Enable reading streams with the HLS protocol. +hls: yes +# Address of the HLS listener. +hlsAddress: :8888 +# Enable TLS/HTTPS on the HLS server. +# This is required for Low-Latency HLS. +hlsEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +hlsServerKey: server.key +# Path to the server certificate. +hlsServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +# This allows to play the HLS stream from an external website. +hlsAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HLS server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +hlsTrustedProxies: [] +# By default, HLS is generated only when requested by a user. +# This option allows to generate it always, avoiding the delay between request and generation. +hlsAlwaysRemux: no +# Variant of the HLS protocol to use. Available options are: +# * mpegts - uses MPEG-TS segments, for maximum compatibility. +# * fmp4 - uses fragmented MP4 segments, more efficient. +# * lowLatency - uses Low-Latency HLS. +hlsVariant: lowLatency +# Number of HLS segments to keep on the server. +# Segments allow to seek through the stream. +# Their number doesn't influence latency. +hlsSegmentCount: 7 +# Minimum duration of each segment. +# A player usually puts 3 segments in a buffer before reproducing the stream. +# The final segment duration is also influenced by the interval between IDR frames, +# since the server changes the duration in order to include at least one IDR frame +# in each segment. +hlsSegmentDuration: 1s +# Minimum duration of each part. +# A player usually puts 3 parts in a buffer before reproducing the stream. +# Parts are used in Low-Latency HLS in place of segments. +# Part duration is influenced by the distance between video/audio samples +# and is adjusted in order to produce segments with a similar duration. +hlsPartDuration: 200ms +# Maximum size of each segment. +# This prevents RAM exhaustion. +hlsSegmentMaxSize: 50M +# Directory in which to save segments, instead of keeping them in the RAM. +# This decreases performance, since reading from disk is less performant than +# reading from RAM, but allows to save RAM. +hlsDirectory: '' +# The muxer will be closed when there are no +# reader requests and this amount of time has passed. +hlsMuxerCloseAfter: 60s + +############################################### +# Global settings -> WebRTC server + +# Enable publishing and reading streams with the WebRTC protocol. +webrtc: yes +# Address of the WebRTC HTTP listener. +webrtcAddress: :8889 +# Enable TLS/HTTPS on the WebRTC server. +webrtcEncryption: no +# Path to the server key. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +webrtcServerKey: server.key +# Path to the server certificate. +webrtcServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +# This allows to play the WebRTC stream from an external website. +webrtcAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the WebRTC server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +webrtcTrustedProxies: [] +# Address of a local UDP listener that will receive connections. +# Use a blank string to disable. +webrtcLocalUDPAddress: :8189 +# Address of a local TCP listener that will receive connections. +# This is disabled by default since TCP is less efficient than UDP and +# introduces a progressive delay when network is congested. +webrtcLocalTCPAddress: '' +# WebRTC clients need to know the IP of the server. +# Gather IPs from interfaces and send them to clients. +webrtcIPsFromInterfaces: yes +# List of interfaces whose IPs will be sent to clients. +# An empty value means to use all available interfaces. +webrtcIPsFromInterfacesList: [] +# List of additional hosts or IPs to send to clients. +webrtcAdditionalHosts: [] +# ICE servers. Needed only when local listeners can't be reached by clients. +# STUN servers allows to obtain and share the public IP of the server. +# TURN/TURNS servers forces all traffic through them. +webrtcICEServers2: [] + # - url: stun:stun.l.google.com:19302 + # if user is "AUTH_SECRET", then authentication is secret based. + # the secret must be inserted into the password field. + # username: '' + # password: '' + # clientOnly: false +# Time to wait for the WebRTC handshake to complete. +webrtcHandshakeTimeout: 10s +# Maximum time to gather video tracks. +webrtcTrackGatherTimeout: 2s +# The maximum time to gather STUN candidates. +webrtcSTUNGatherTimeout: 5s + +############################################### +# Global settings -> SRT server + +# Enable publishing and reading streams with the SRT protocol. +srt: yes +# Address of the SRT listener. +srtAddress: :8890 + +############################################### +# Default path settings + +# Settings in "pathDefaults" are applied anywhere, +# unless they are overridden in "paths". +pathDefaults: + + ############################################### + # Default path settings -> General + + # Source of the stream. This can be: + # * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client + # * rtsp://existing-url -> the stream is pulled from another RTSP server / camera + # * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS + # * rtmp://existing-url -> the stream is pulled from another RTMP server / camera + # * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS + # * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera + # * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera with HTTPS + # * udp://ip:port -> the stream is pulled with UDP, by listening on the specified IP and port + # * srt://existing-url -> the stream is pulled from another SRT server / camera + # * whep://existing-url -> the stream is pulled from another WebRTC server / camera + # * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS + # * redirect -> the stream is provided by another path or server + # * rpiCamera -> the stream is provided by a Raspberry Pi Camera + # The following variables can be used in the source string: + # * $MTX_QUERY: query parameters (passed by first reader) + # * $G1, $G2, ...: regular expression groups, if path name is + # a regular expression. + source: publisher + # If the source is a URL, and the source certificate is self-signed + # or invalid, you can provide the fingerprint of the certificate in order to + # validate it anyway. It can be obtained by running: + # openssl s_client -connect source_ip:source_port /dev/null | sed -n '/BEGIN/,/END/p' > server.crt + # openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':' + sourceFingerprint: + # If the source is a URL, it will be pulled only when at least + # one reader is connected, saving bandwidth. + sourceOnDemand: no + # If sourceOnDemand is "yes", readers will be put on hold until the source is + # ready or until this amount of time has passed. + sourceOnDemandStartTimeout: 10s + # If sourceOnDemand is "yes", the source will be closed when there are no + # readers connected and this amount of time has passed. + sourceOnDemandCloseAfter: 10s + # Maximum number of readers. Zero means no limit. + maxReaders: 0 + # SRT encryption passphrase required to read from this path. + srtReadPassphrase: + # If the stream is not available, redirect readers to this path. + # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. + fallback: + # Route original absolute timestamps of RTSP and WebRTC frames, instead of replacing them. + useAbsoluteTimestamp: false + + ############################################### + # Default path settings -> Record + + # Record streams to disk. + record: no + # Path of recording segments. + # Extension is added automatically. + # Available variables are %path (path name), %Y %m %d (year, month, day), + # %H %M %S (hours, minutes, seconds), %f (microseconds), %z (time zone), %s (unix epoch). + recordPath: ./recordings/%path/%Y-%m-%d_%H-%M-%S-%f + # Format of recorded segments. + # Available formats are "fmp4" (fragmented MP4) and "mpegts" (MPEG-TS). + recordFormat: fmp4 + # fMP4 segments are concatenation of small MP4 files (parts), each with this duration. + # MPEG-TS segments are concatenation of 188-bytes packets, flushed to disk with this period. + # When a system failure occurs, the last part gets lost. + # Therefore, the part duration is equal to the RPO (recovery point objective). + recordPartDuration: 1s + # This prevents RAM exhaustion. + recordMaxPartSize: 50M + # Minimum duration of each segment. + recordSegmentDuration: 1h + # Delete segments after this timespan. + # Set to 0s to disable automatic deletion. + recordDeleteAfter: 1d + + ############################################### + # Default path settings -> Publisher source (when source is "publisher") + + # Allow another client to disconnect the current publisher and publish in its place. + overridePublisher: yes + # SRT encryption passphrase required to publish to this path. + srtPublishPassphrase: + + ############################################### + # Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL) + + # Transport protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp". + rtspTransport: automatic + # Support sources that don't provide server ports or use random server ports. This is a security issue + # and must be used only when interacting with sources that require it. + rtspAnyPort: no + # Range header to send to the source, in order to start streaming from the specified offset. + # available values: + # * clock: Absolute time + # * npt: Normal Play Time + # * smpte: SMPTE timestamps relative to the start of the recording + rtspRangeType: + # Available values: + # * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z + # * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + # * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + rtspRangeStart: + + ############################################### + # Default path settings -> Redirect source (when source is "redirect") + + # path which clients will be redirected to. + # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. + sourceRedirect: + + ############################################### + # Default path settings -> Raspberry Pi Camera source (when source is "rpiCamera") + + # ID of the camera. + rpiCameraCamID: 0 + # Whether this is a secondary stream. + rpiCameraSecondary: false + # Width of frames. + rpiCameraWidth: 1920 + # Height of frames. + rpiCameraHeight: 1080 + # Flip horizontally. + rpiCameraHFlip: false + # Flip vertically. + rpiCameraVFlip: false + # Brightness [-1, 1]. + rpiCameraBrightness: 0 + # Contrast [0, 16]. + rpiCameraContrast: 1 + # Saturation [0, 16]. + rpiCameraSaturation: 1 + # Sharpness [0, 16]. + rpiCameraSharpness: 1 + # Exposure mode. + # values: normal, short, long, custom. + rpiCameraExposure: normal + # Auto-white-balance mode. + # values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom. + rpiCameraAWB: auto + # Auto-white-balance fixed gains. This can be used in place of rpiCameraAWB. + # format: [red,blue]. + rpiCameraAWBGains: [0, 0] + # Denoise operating mode. + # values: off, cdn_off, cdn_fast, cdn_hq. + rpiCameraDenoise: "off" + # Fixed shutter speed, in microseconds. + rpiCameraShutter: 0 + # Metering mode of the AEC/AGC algorithm. + # values: centre, spot, matrix, custom. + rpiCameraMetering: centre + # Fixed gain. + rpiCameraGain: 0 + # EV compensation of the image [-10, 10]. + rpiCameraEV: 0 + # Region of interest, in format x,y,width,height (all normalized between 0 and 1). + rpiCameraROI: + # Whether to enable HDR on Raspberry Camera 3. + rpiCameraHDR: false + # Tuning file. + rpiCameraTuningFile: + # Sensor mode, in format [width]:[height]:[bit-depth]:[packing] + # bit-depth and packing are optional. + rpiCameraMode: + # frames per second. + rpiCameraFPS: 30 + # Autofocus mode. + # values: auto, manual, continuous. + rpiCameraAfMode: continuous + # Autofocus range. + # values: normal, macro, full. + rpiCameraAfRange: normal + # Autofocus speed. + # values: normal, fast. + rpiCameraAfSpeed: normal + # Lens position (for manual autofocus only), will be set to focus to a specific distance + # calculated by the following formula: d = 1 / value + # Examples: 0 moves the lens to infinity. + # 0.5 moves the lens to focus on objects 2m away. + # 2 moves the lens to focus on objects 50cm away. + rpiCameraLensPosition: 0.0 + # Specifies the autofocus window, in the form x,y,width,height where the coordinates + # are given as a proportion of the entire image. + rpiCameraAfWindow: + # Manual flicker correction period, in microseconds. + rpiCameraFlickerPeriod: 0 + # Enables printing text on each frame. + rpiCameraTextOverlayEnable: false + # Text that is printed on each frame. + # format is the one of the strftime() function. + rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX' + # Codec. Available values: auto, hardwareH264, softwareH264, mjpeg. + rpiCameraCodec: auto + # Period between H264 IDR frames. + rpiCameraIDRPeriod: 60 + # H264 Bitrate. + rpiCameraBitrate: 5000000 + # H264 profile. + rpiCameraProfile: main + # H264 level. + rpiCameraLevel: '4.1' + # JPEG quality. + rpiCameraJPEGQuality: 60 + + ############################################### + # Default path settings -> Hooks + + # Command to run when this path is initialized. + # This can be used to publish a stream when the server is launched. + # This is terminated with SIGINT when the program closes. + # The following environment variables are available: + # * MTX_PATH: path name + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnInit: + # Restart the command if it exits. + runOnInitRestart: no + + # Command to run when this path is requested by a reader + # and no one is publishing to this path yet. + # This can be used to publish a stream on demand. + # This is terminated with SIGINT when there are no readers anymore. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by first reader) + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnDemand: + # Restart the command if it exits. + runOnDemandRestart: no + # Readers will be put on hold until the runOnDemand command starts publishing + # or until this amount of time has passed. + runOnDemandStartTimeout: 10s + # The command will be closed when there are no + # readers connected and this amount of time has passed. + runOnDemandCloseAfter: 10s + # Command to run when there are no readers anymore. + # Environment variables are the same of runOnDemand. + runOnUnDemand: + + # Command to run when the stream is ready to be read, whenever it is + # published by a client or pulled from a server / camera. + # This is terminated with SIGINT when the stream is not ready anymore. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by publisher) + # * MTX_SOURCE_TYPE: source type + # * MTX_SOURCE_ID: source ID + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnReady: + # Restart the command if it exits. + runOnReadyRestart: no + # Command to run when the stream is not available anymore. + # Environment variables are the same of runOnReady. + runOnNotReady: + + # Command to run when a client starts reading. + # This is terminated with SIGINT when a client stops reading. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by reader) + # * MTX_READER_TYPE: reader type + # * MTX_READER_ID: reader ID + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnRead: + # Restart the command if it exits. + runOnReadRestart: no + # Command to run when a client stops reading. + # Environment variables are the same of runOnRead. + runOnUnread: + + # Command to run when a recording segment is created. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_SEGMENT_PATH: segment file path + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnRecordSegmentCreate: + + # Command to run when a recording segment is complete. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_SEGMENT_PATH: segment file path + # * MTX_SEGMENT_DURATION: segment duration + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnRecordSegmentComplete: + +############################################### +# Path settings + +# Settings in "paths" are applied to specific paths, and the map key +# is the name of the path. +# Any setting in "pathDefaults" can be overridden here. +# It's possible to use regular expressions by using a tilde as prefix, +# for example "~^(test1|test2)$" will match both "test1" and "test2", +# for example "~^prefix" will match all paths that start with "prefix". +paths: + # example: + # my_camera: + # source: rtsp://my_camera + + # Settings under path "all_others" are applied to all paths that + # do not match another entry. + all_others: diff --git a/RTSPServer/mediamtx_v1.13.1_linux_arm64.tar.gz b/RTSPServer/mediamtx_v1.13.1_linux_arm64.tar.gz new file mode 100644 index 0000000..82ef675 Binary files /dev/null and b/RTSPServer/mediamtx_v1.13.1_linux_arm64.tar.gz differ diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..49300a9 --- /dev/null +++ b/Readme.md @@ -0,0 +1,46 @@ +# 程序功能清单 +## DeviceActivate(已测试) +功能: +本程序用于设备激活 +当检测配置文件发现设备未激活时,则系统阻塞 +进行激活,并保存密文 +当检测到设备已被激活后,启动验证程序,并退出本程序 + +## softWareInit(已测试) +``` +功能: +本程序用于设备验证 +验证通过执行以下功能: +1.启动RTSP推流 +2.启动上传服务,用于上传最新报警图片 +3.启动mqtt,与App进行数据交互,设定报警等参数 +认证失败则会退出程序 +``` +## GPIOSignal(已测试) +功能: +用于输出高低电平,提供报警信号 + +## PyApp(已测试) +功能: +上传服务,用于检测是否存在最新图片,以进行上传报警图片服务服务 +通过设备验证后,由StartService进行启动 + +## StartService(已测试) +功能: +启动上传服务 + +## VideoProsessing(已测试) +功能: +本程序用于视频分流 +1.推流摄像头画面,使用UDP原生协议进行推流,交由YOLO模型进行处理 +2.接收YOLO传来的坐标和度数据 +3.根据获取到的数据绘制边框和相应数据 +4.根据距离信息进行报警和图片视频保存 +5.输出处理完毕的视频帧 + + +## Identification(已测试) +本程序用于 设备出厂获取 唯一标识符进行 wifi设置和修改 +向云端发送访问请求(ICCID号),并显示请求次数 +接收云端传传来的标识符,并保存至本地,根据表示符进行wifi设置 +第二次启动时,自动检测是否已进行初始化,若是,直接从配置文件中获取唯一标识符 \ No newline at end of file diff --git a/StartService/bin/start b/StartService/bin/start new file mode 100755 index 0000000..4d86317 Binary files /dev/null and b/StartService/bin/start differ diff --git a/StartService/bin/start.sh b/StartService/bin/start.sh new file mode 100644 index 0000000..7941dd7 --- /dev/null +++ b/StartService/bin/start.sh @@ -0,0 +1,14 @@ +#开启pubimg.service,上传最新的照片至服务器 +sudo systemctl start pubimg.service + +# #开启虚拟设备 +sudo modprobe v4l2loopback video_nr=10 card_label="VirtualCam10" exclusive_caps=1 + +# #开启YOLO检测,并写入视频流 +python3 /opt/rknn-yolov11/src/yolov11_stereo_distance.py \ + --enable-v4l2-out \ + --v4l2-device /dev/video10 \ + --v4l2-out-pix-fmt rgb24 > /dev/null 2>&1 + +# #开启fastapi +# sudo systemctl start fastApi.service \ No newline at end of file diff --git a/StartService/bin/stop.sh b/StartService/bin/stop.sh new file mode 100644 index 0000000..fd5ba69 --- /dev/null +++ b/StartService/bin/stop.sh @@ -0,0 +1,3 @@ +sudo systemctl stop fastApi.service + +sudo systemctl stop pubimg.service \ No newline at end of file diff --git a/StartService/src/makefile b/StartService/src/makefile new file mode 100644 index 0000000..1b98836 --- /dev/null +++ b/StartService/src/makefile @@ -0,0 +1,9 @@ +all:start + +start: + g++ -g -o start start.cpp + + mv start ../bin + +clean: + rm -rf start \ No newline at end of file diff --git a/StartService/src/start.cpp b/StartService/src/start.cpp new file mode 100644 index 0000000..d2b8776 --- /dev/null +++ b/StartService/src/start.cpp @@ -0,0 +1,62 @@ +/* + 本程序为BSD服务启动程序,主要用于进行BSD系统的服务启动 +*/ +#include + +using namespace std; + +void StartDeep(); + +int main(int argc, char **argv) +{ + if (argc != 2) + { + cout << "Using:./start start/stop" << endl; + return -1; + } + + string commnd = ""; + + string action(argv[1]); + if (action == "start") + { + commnd = "echo 'orangepi' | sh /home/orangepi/RKApp/StartService/bin/start.sh"; + } + else if (action == "stop") + { + commnd = "echo 'orangepi' | sh /home/orangepi/RKApp/StartService/bin/stop.sh"; + } + else + { + cout << "Unknown action: " << action << "\nUsing: ./start start|stop" << endl; + return -1; + } + + system(commnd.c_str()); + + // StartDeep(); + + return 0; +} + +void StartDeep() +{ + // string cmd = "/home/orangepi/miniconda3/bin/python3 /opt/rknn-yolov11/src/yolov11_stereo_distance.py \ + // --enable-v4l2-out \ + // --v4l2-device /dev/video10 \ + // --v4l2-out-pix-fmt rgb24 > /dev/null 2>&1"; + + string cmd = "python3 /opt/rknn-yolov11/src/yolov11_stereo_distance.py \ + --enable-v4l2-out \ + --v4l2-device /dev/video10 \ + --v4l2-out-pix-fmt rgb24 > /dev/null 2>&1"; + + try + { + system(cmd.c_str()); + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } +} \ No newline at end of file diff --git a/VideoProsessing/NetraLib/README.md b/VideoProsessing/NetraLib/README.md new file mode 100644 index 0000000..c680108 --- /dev/null +++ b/VideoProsessing/NetraLib/README.md @@ -0,0 +1,128 @@ +# NetraLib +c/c++基本开发库 + +# TCP 服务端操作 +包括多线程客户端连接,指定客户端数据的收发等等功能 + +# Linux 中屏蔽所有信号操作 +屏蔽所有信号,以防止意外退出 + + +# 写文件操作 +允许原文本进行覆盖写,追加写 +允许二进制进行覆盖写,追加写 +允许在特定位置后面进行插入覆盖操作 +允许删除特定字段后面所有内容在进行写操作 +可以根据需要计算特定符号最后一个字节或者第一个字节所在位置所在位置 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 读文件操作 +支持全文读取(文本和二进制模式) +支持按行读取文本内容 +支持按指定字节数读取数据 +支持计算第一个指定字节序列结束位置(包含该序列本身)的字节数 +提供文件是否存在和文件大小查询 +支持重置文件读取位置,实现多次读取 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 字符串操作 +支持左右空格删除 +支持格式化输出 + + +# Http请求 +提供基于 `cpp-httplib` 的简易 HTTP 客户端封装 `NetRequest`,支持: +1. 同步/异步 GET、POST(JSON、表单) +2. 连接/读写超时设置、Keep-Alive +3. 并发请求上限控制 +4. 可选内存缓存(GET 命中时不发起网络请求) +5. 简单日志回调与性能统计 +6. 断点续传下载到本地文件 + +使用步骤: + +1) 引入头文件,配置目标地址 +```cpp +#include "NetRequest.hpp" + +ntq::RequestOptions opt; +opt.scheme = "http"; // 或 https +opt.host = "127.0.0.1"; // 服务器地址 +opt.port = 8080; // 端口(https 一般 443) +opt.base_path = "/api"; // 可选统一前缀 +opt.connect_timeout_ms = 3000; +opt.read_timeout_ms = 8000; +opt.write_timeout_ms = 8000; +opt.default_headers = { {"Authorization", "Bearer TOKEN"} }; // 可选 + +ntq::NetRequest req(opt); +req.setMaxConcurrentRequests(4); +req.enableCache(std::chrono::seconds(10)); +``` + +2) 发送 GET 请求 +```cpp +auto r = req.Get("/info"); +if (r && r->status == 200) { + // r->body 为返回内容 +} + +// 带查询参数与额外请求头 +httplib::Params q = {{"q","hello"},{"page","1"}}; +httplib::Headers h = {{"X-Req-Id","123"}}; +auto r2 = req.Get("/search", q, h); +``` + +3) 发送 POST 请求 +```cpp +// JSON(Content-Type: application/json) +std::string json = R"({"name":"orangepi","mode":"demo"})"; +auto p1 = req.PostJson("/set", json); + +// 表单(application/x-www-form-urlencoded) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = req.PostForm("/login", form); +``` + +4) 异步调用 +```cpp +auto fut = req.GetAsync("/info"); +auto res = fut.get(); // 与同步用法一致 +``` + +5) 下载到本地(支持断点续传) +```cpp +bool ok = req.DownloadToFile("/files/pkg.bin", "/tmp/pkg.bin", {}, /*resume*/true); +``` + +6) 统计信息 +```cpp +auto s = req.getStats(); +// s.total_requests / s.total_errors / s.last_latency_ms / s.avg_latency_ms +``` + +说明: +- 若使用 HTTPS,需在编译时添加 `-DCPPHTTPLIB_OPENSSL_SUPPORT` 并链接 `-lssl -lcrypto`,且将 `opt.scheme` 设为 `"https"`、端口通常为 `443`。 +- `base_path` 与各函数传入的 `path` 会自动合并,例如 `base_path="/api"` 且 `Get("/info")` 实际请求路径为 `/api/info`。 + +7) 快速用法(无需实例化) +```cpp +using ntq::NetRequest; + +// 直接传完整 URL 发起 GET +auto g = NetRequest::QuickGet("http://127.0.0.1:8080/api/info"); + +// 直接传完整 URL 发起 POST(JSON) +auto p1 = NetRequest::QuickPostJson( + "http://127.0.0.1:8080/api/set", + R"({"name":"orangepi","mode":"demo"})" +); + +// 直接传完整 URL 发起 POST(表单) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = NetRequest::QuickPostForm("http://127.0.0.1:8080/login", form); +``` diff --git a/VideoProsessing/NetraLib/include/NetRequest.hpp b/VideoProsessing/NetraLib/include/NetRequest.hpp new file mode 100644 index 0000000..3cb473c --- /dev/null +++ b/VideoProsessing/NetraLib/include/NetRequest.hpp @@ -0,0 +1,344 @@ +/* +本文件 +网络请求类需要实现以下功能: +1. 发送网络请求 +2. 接收网络响应 +3. 处理网络请求和响应 +4. 实现网络请求和响应的回调函数 +5. 实现网络请求和响应的错误处理 +6. 实现网络请求和响应的日志记录 +7. 实现网络请求和响应的性能统计 +8. 实现网络请求和响应的并发控制 +9. 实现网络请求和响应的缓存管理 +10. 实现网络请求和响应的断点续传 +11. 实现网络请求和响应的断点续传 +12. 实现网络请求和响应的断点续传 +13. 实现网络请求和响应的断点续传 +14. 实现网络请求和响应的断点续传 +*/ +#pragma once +#include "httplib.h" +#include +#include +#include +#include + +// C++17/14 可选类型回退适配:统一使用 ntq::optional / ntq::nullopt +#if defined(__has_include) + #if __has_include() + #include + // 仅当启用了 C++17 或库声明了 optional 功能时,才使用 std::optional + #if defined(__cpp_lib_optional) || (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif +#else + // 无 __has_include:按语言级别判断 + #if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + #include + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #else + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #endif +#endif + +namespace ntq +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief HTTP 响应对象 + * + * 用于承载一次请求返回的状态码、正文与响应头; + * 当 from_cache 为 true 时,表示该响应来自本地缓存(而非实时网络)。 + */ + struct HttpResponse + { + int status = 0; ///< HTTP 状态码(例如 200, 404 等) + std::string body; ///< 响应正文 + httplib::Headers headers; ///< 响应头(大小写不敏感) + bool from_cache = false; ///< 是否来自缓存 + }; + + /** + * @brief 错误码定义 + */ + enum class ErrorCode + { + None = 0, ///< 无错误 + Network, ///< 网络错误(连接失败/发送失败/接收失败等) + Timeout, ///< 超时 + Canceled, ///< 被取消 + InvalidURL, ///< URL 非法 + IOError, ///< 本地 IO 错误(如写文件失败) + SSL, ///< SSL/HTTPS 相关错误 + Unknown ///< 未分类错误 + }; + + /** + * @brief 请求级别的参数配置 + * + * 包含基础连接信息(协议、主机、端口)与默认头部、超时设置等。 + */ + struct RequestOptions + { + std::string scheme = "http"; ///< 协议:http 或 https + std::string host; ///< 目标主机名或 IP(必填) + int port = 80; ///< 端口,http 默认为 80,https 通常为 443 + std::string base_path; ///< 可选的统一前缀(例如 "/api/v1") + + int connect_timeout_ms = 5000; ///< 连接超时(毫秒) + int read_timeout_ms = 10000; ///< 读取超时(毫秒) + int write_timeout_ms = 10000; ///< 写入超时(毫秒) + bool keep_alive = true; ///< 是否保持连接(Keep-Alive) + + httplib::Headers default_headers; ///< 默认头部,随所有请求发送 + }; + + /** + * @class NetRequest + * @brief HTTP 客户端封装(基于 cpp-httplib) + * + * 提供同步和异步的 GET/POST 请求、并发限制、可选缓存、日志回调、 + * 性能统计以及断点续传下载到文件等能力。 + */ + class NetRequest + { + public: + using LogCallback = std::function; ///< 日志回调类型 + + /** + * @brief 运行时统计数据 + */ + struct Stats + { + uint64_t total_requests = 0; ///< 累计请求次数 + uint64_t total_errors = 0; ///< 累计失败次数 + double last_latency_ms = 0.0;///< 最近一次请求耗时(毫秒) + double avg_latency_ms = 0.0; ///< 指数平滑后的平均耗时(毫秒) + }; + + /** + * @brief 构造函数 + * @param options 请求参数配置(目标地址、超时、默认头部等) + */ + explicit NetRequest(const RequestOptions &options); + /** + * @brief 析构函数 + */ + ~NetRequest(); + + /** + * @brief 设置日志回调 + * @param logger 回调函数,接受一行日志字符串 + */ + void setLogger(LogCallback logger); + /** + * @brief 设置最大并发请求数 + * @param n 并发上限(最小值为 1) + */ + void setMaxConcurrentRequests(size_t n); + /** + * @brief 启用内存缓存 + * @param ttl 缓存有效期(超时后自动失效) + */ + void enableCache(std::chrono::milliseconds ttl); + /** + * @brief 禁用并清空缓存 + */ + void disableCache(); + + /** + * @brief 发送同步 GET 请求 + * @param path 资源路径(会与 base_path 合并) + * @param query 查询参数(会拼接为 ?k=v&...) + * @param headers 额外请求头(与默认头部合并) + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional Get(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST JSON 请求 + * @param path 资源路径 + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST 表单请求(application/x-www-form-urlencoded) + * @param path 资源路径 + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 GET 请求 + * @return std::future,用于获取响应结果 + */ + std::future> GetAsync(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST JSON 请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST 表单请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 下载文件到本地,支持断点续传 + * @param path 资源路径 + * @param local_file 本地保存路径 + * @param headers 额外头部 + * @param resume 是否启用续传(Range) + * @param chunk_size 预留参数:分块大小(当前由 httplib 内部回调驱动) + * @param err 可选错误码输出 + * @return true 下载成功(200 或 206),false 失败 + */ + bool DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers = {}, + bool resume = true, + size_t chunk_size = 1 << 15, + ErrorCode *err = nullptr); + + /** + * @brief 获取统计数据快照 + */ + Stats getStats() const; + + /** + * @brief 便捷:直接用完整 URL 发起 GET(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickGet(const std::string &url, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST JSON(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST 表单(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + private: + struct Impl; + Impl *impl_; + }; +} \ No newline at end of file diff --git a/VideoProsessing/NetraLib/include/Netra.hpp b/VideoProsessing/NetraLib/include/Netra.hpp new file mode 100644 index 0000000..362ae28 --- /dev/null +++ b/VideoProsessing/NetraLib/include/Netra.hpp @@ -0,0 +1,426 @@ +#pragma once +#include "QCL_Include.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @class TcpServer + * @brief 简单的多线程TCP服务器类,支持多个客户端连接,数据收发及断开处理 + * + * 该类使用一个线程专门用于监听客户端连接, + * 每当有客户端连接成功时,为其创建一个独立线程处理该客户端的数据收发。 + * 线程安全地管理所有客户端Socket句柄。 + */ + class TcpServer + { + public: + /** + * @brief 构造函数,指定监听端口 + * @param port 服务器监听端口号 + */ + TcpServer(int port); + + /** + * @brief 析构函数,自动调用 stop() 停止服务器并清理资源 + */ + ~TcpServer(); + + /** + * @brief 启动服务器,创建监听socket,开启监听线程 + * @return 启动成功返回true,失败返回false + */ + bool start(); + + /** + * @brief 停止服务器,关闭所有连接,释放资源,等待所有线程退出 + */ + void stop(); + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端Socket描述符 + * @param message 发送的字符串消息 + */ + void sendToClient(int clientSock, const std::string &message); + + /** + * @brief 从指定客户端接收数据(单次调用) + * @param clientSock 客户端Socket描述符 + * @param flag:false 非阻塞模式,true 阻塞模式 + */ + std::string receiveFromClient(int clientSock, bool flag = true); + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *getClientIPAndPort(int clientSock); + + /** + * @brief 获取当前所有已连接客户端Socket的副本 + * @return 包含所有客户端Socket的vector,线程安全 + */ + std::vector getClientSockets(); + + /** + * @brief 从服务器的客户端列表中移除并关闭一个客户端socket + * @param clientSock 客户端Socket描述符 + */ + void removeClient(int clientSock); + + /** + * @brief 非阻塞探测客户端是否已断开(不消耗数据) + * @param clientSock 客户端Socket描述符 + * @return true 已断开或发生致命错误;false 仍然存活或暂无数据 + */ + bool isClientDisconnected(int clientSock); + + private: + /** + * @brief 监听并接受新的客户端连接(运行在独立线程中) + */ + void acceptClients(); + + private: + int serverSock_; ///< 服务器监听Socket描述符 + int port_; ///< 服务器监听端口 + std::atomic running_; ///< 服务器运行状态标志(线程安全) + std::vector clientThreads_; ///< 用于处理每个客户端的线程集合 + std::thread acceptThread_; ///< 负责监听新连接的线程 + std::mutex clientsMutex_; ///< 保护clientSockets_的互斥锁 + std::vector clientSockets_; ///< 当前所有连接的客户端Socket集合 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief 文件写入工具类(线程安全) + * + * 该类支持多种文件写入方式: + * - 覆盖写文本 + * - 追加写文本 + * - 按位置覆盖原文写入 + * - 二进制覆盖写 + * - 二进制追加写 + * + * 特点: + * - 文件不存在时自动创建 + * - 支持覆盖和追加两种模式 + * - 支持二进制模式,适合写入非文本数据 + * - 内部使用 std::mutex 实现线程安全 + */ + class WriteFile + { + public: + /** + * @brief 构造函数 + * @param filePath 文件路径 + */ + explicit WriteFile(const std::string &filePath); + + /** + * @brief 覆盖写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteText(const std::string &content); + + /** + * @brief 追加写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendText(const std::string &content); + + /** + * @brief 覆盖写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteBinary(const std::vector &data); + + /** + * @brief 追加写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendBinary(const std::vector &data); + + /** + * @brief 计算第一个指定字节序列前的字节数 + * @param pattern 要查找的字节序列 + * @param includePattern true 表示返回值包含 pattern 自身长度,false 表示不包含 + * @return size_t 字节数,如果文件没打开或 pattern 为空则返回 0 + */ + size_t countBytesPattern(const std::string &pattern, bool includePattern = false); + + /** + * @brief 在文件中查找指定字节序列并在其后写入内容,如果不存在则追加到文件末尾 + * @param pattern 要查找的字节序列 + * @param content 要写入的内容 + * @return true 写入成功,false 文件打开失败 + * + * 功能说明: + * 1. 若文件中存在 pattern,则删除 pattern 之后的所有内容,并在其后插入 content。 + * 2. 若文件中不存在 pattern,则在文件末尾追加 content,若末尾无换行符则先补充换行。 + */ + bool writeAfterPatternOrAppend(const std::string &pattern, const std::string &content); + + /** + * @brief 在文件指定位置之后插入内容 + * @param content 要插入的内容 + * @param pos 插入位置(从文件开头算起的字节偏移量) + * @param length 插入的长度(>= content.size() 时,多余部分用空字节填充;< content.size() 时只截取前 length 个字节) + * @return true 插入成功,false 文件打开失败或参数不合法 + * + * 功能说明: + * 1. 不会覆盖原有数据,而是将 pos 之后的内容整体向后移动 length 个字节。 + * 2. 如果 length > content.size(),则在 content 后补充 '\0'(或空格,可按需求改)。 + * 3. 如果 length < content.size(),则只写入 content 的前 length 个字节。 + * 4. 文件整体大小会增加 length 个字节。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * insertAfterPos("XY", 2, 3) // 在索引 2 后插入 + * 结果: "ABX Y\0CDEFG" (这里 \0 代表补充的空字节) + */ + bool insertAfterPos(const std::string &content, size_t pos, size_t length); + + /** + * @brief 在文件指定位置覆盖写入内容 + * @param content 要写入的内容 + * @param pos 覆盖起始位置(从文件开头算起的字节偏移量) + * @param length 覆盖长度 + * @return true 覆盖成功,false 文件打开失败或 pos 越界 + * + * 功能说明: + * 1. 从 pos 开始覆盖 length 个字节,不会移动或增加文件大小。 + * 2. 如果 content.size() >= length,则只写入前 length 个字节。 + * 3. 如果 content.size() < length,则写入 content,并用 '\0' 补齐至 length。 + * 4. 如果 pos + length 超过文件末尾,则只覆盖到文件尾部,不会越界。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * overwriteAtPos("XY", 2, 3) + * 结果: "ABXYEFG" (原 "CDE" 被 "XY\0" 覆盖,\0 实际不可见) + */ + bool overwriteAtPos(const std::string &content, size_t pos, size_t length); + + void close(); + + private: + std::string filePath_; ///< 文件路径 + std::mutex writeMutex_; ///< 线程锁,保证多线程写入安全 + + /** + * @brief 通用文本写入接口(线程安全) + * @param content 要写入的内容 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeToFile(const std::string &content, std::ios::openmode mode); + + /** + * @brief 通用二进制写入接口(线程安全) + * @param data 要写入的二进制数据 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeBinary(const std::vector &data, std::ios::openmode mode); + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief ReadFile 类 - 读文件操作工具类 + * + * 功能: + * 1. 读取全文(文本 / 二进制) + * 2. 按行读取 + * 3. 按字节数读取 + * 4. 获取指定字节序列前的字节数(包含该字节序列) + * 5. 检查文件是否存在 + * 6. 获取文件大小 + * + * 设计: + * - 与 WriteFile 类风格保持一致 + * - 支持文本文件与二进制文件 + * - 自动关闭文件(析构时) + * - 内部使用 std::mutex 实现线程安全 + */ + class ReadFile + { + public: + /** + * @brief 构造函数 + * @param filename 文件路径 + */ + explicit ReadFile(const std::string &filename); + + /** + * @brief 析构函数,自动关闭文件 + */ + ~ReadFile(); + + /** + * @brief 打开文件(以二进制方式) + * @return true 打开成功 + * @return false 打开失败 + */ + bool Open(); + + /** + * @brief 关闭文件 + */ + void Close(); + + /** + * @brief 文件是否已经打开 + */ + bool IsOpen() const; + + /** + * @brief 读取全文(文本模式) + * @return 文件内容字符串 + */ + std::string ReadAllText(); + + /** + * @brief 读取全文(二进制模式) + * @return 文件内容字节数组 + */ + std::vector ReadAllBinary(); + + /** + * @brief 按行读取文本 + * @return 每行作为一个字符串的 vector + */ + std::vector ReadLines(); + + /** + * @brief 读取指定字节数 + * @param count 要读取的字节数 + * @return 实际读取到的字节数据 + */ + std::vector ReadBytes(size_t count); + + /** + * @brief 获取指定字节序列前的字节数(包含该字节序列) + * @param marker 要查找的字节序列(可能不止一个字节) + * @return 如果找到,返回前面部分字节数;找不到返回0 + */ + size_t GetBytesBefore(const std::string &marker, bool includeMarker = false); + + /** + * @brief 从指定位置读取指定字节数,默认读取到文件末尾 + * @param pos 起始位置(字节偏移) + * @param count 要读取的字节数,默认为0表示读取到文件末尾 + * @return 读取到的字节数据 + */ + std::vector ReadBytesFrom(size_t pos, size_t count = 0); + + /** + * @brief 检查文件是否存在 + */ + bool FileExists() const; + + /** + * @brief 获取文件大小(字节数) + */ + size_t GetFileSize() const; + + /** + * @brief 重置读取位置到文件开头 + */ + void Reset(); + + private: + std::string filename_; // 文件路径 + std::ifstream file_; // 文件流对象 + mutable std::mutex mtx_; // 可变,保证 const 方法也能加锁 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals(); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 字符串操作 + // 去除字符串的左空格 + std::string Ltrim(const std::string &s); + + // 去除字符串右侧的空格 + std::string Rtrim(const std::string &s); + + // 去除字符串左右两侧的空格 + std::string LRtrim(const std::string &s); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // c++进行格式化输出 + // 通用类型转字符串 + template + std::string to_string_any(const T &value) + { + std::ostringstream oss; + oss << value; + return oss.str(); + } + + // 递归获取 tuple 中 index 对应参数 + template + std::string get_tuple_arg(const Tuple &tup, std::size_t index) + { + if constexpr (I < std::tuple_size_v) + { + if (I == index) + return to_string_any(std::get(tup)); + else + return get_tuple_arg(tup, index); + } + else + { + throw std::runtime_error("Too few arguments for format string"); + } + } + + // format 函数 + template + std::string format(const std::string &fmt, const Args &...args) + { + std::ostringstream oss; + std::tuple tup(args...); + size_t pos = 0; + size_t arg_idx = 0; + + while (pos < fmt.size()) + { + if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '{') + { + oss << '{'; + pos += 2; + } + else if (fmt[pos] == '}' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << '}'; + pos += 2; + } + else if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << get_tuple_arg(tup, arg_idx++); + pos += 2; + } + else + { + oss << fmt[pos++]; + } + } + + if (arg_idx < sizeof...(Args)) + throw std::runtime_error("Too many arguments for format string"); + + return oss.str(); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +} diff --git a/VideoProsessing/NetraLib/include/QCL_Include.hpp b/VideoProsessing/NetraLib/include/QCL_Include.hpp new file mode 100644 index 0000000..52a20fb --- /dev/null +++ b/VideoProsessing/NetraLib/include/QCL_Include.hpp @@ -0,0 +1,55 @@ +/** + * @file qcl_include.hpp + * @brief 通用C++开发头文件集合(Linux环境) + * @note 使用前确保目标平台支持C++17标准 + */ +#ifndef QCL_INCLUDE_HPP +#define QCL_INCLUDE_HPP + +// ==================== C/C++基础运行时库 ==================== +#include // 标准输入输出流(cin/cout/cerr) +#include // std::string类及相关操作 +#include // C风格字符串操作(strcpy/strcmp等) +#include // 通用工具函数(atoi/rand/malloc等) +#include // C风格IO(printf/scanf) +#include // 断言宏(调试期检查) +#include // 数学函数(sin/pow等) +#include // 时间处理(time/clock) +#include // 信号处理(signal/kill) +#include // 智能指针 + +// ==================== STL容器与算法 ==================== +#include // 动态数组(连续内存容器) +#include // 双向链表 +#include // 双端队列 +#include // 有序键值对(红黑树实现) +#include // 有序集合 +#include // 哈希表实现的键值对 +#include // 哈希表实现的集合 +#include // 栈适配器(LIFO) +#include // 队列适配器(FIFO) +#include // 通用算法(sort/find等) +#include // 数值算法(accumulate等) +#include // 迭代器相关 + +// ==================== 字符串与流处理 ==================== +#include // 字符串流(内存IO) +#include // 文件流(文件IO) +#include // 流格式控制(setw/setprecision) +#include // 正则表达式 +#include // 文件系统(C++17) +#include + +// ==================== 并发编程支持 ==================== +#include // 线程管理(std::thread) +#include // 互斥锁(mutex/lock_guard) +#include // 原子操作(线程安全变量) +#include // 条件变量(线程同步) + +// ==================== Linux网络编程 ==================== +#include // 套接字基础API(socket/bind) +#include // IPV4/IPV6地址结构体 +#include // 地址转换函数(inet_pton等) +#include // POSIX API(close/read/write) + +#endif // QCL_INCLUDE_HPP diff --git a/VideoProsessing/NetraLib/include/README.md b/VideoProsessing/NetraLib/include/README.md new file mode 100644 index 0000000..984ab89 --- /dev/null +++ b/VideoProsessing/NetraLib/include/README.md @@ -0,0 +1,850 @@ +cpp-httplib +=========== + +[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions) + +A C++11 single-file header-only cross platform HTTP/HTTPS library. + +It's extremely easy to setup. Just include the **httplib.h** file in your code! + +NOTE: This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. + +Simple examples +--------------- + +#### Server (Multi-threaded) + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Server svr; + +// HTTPS +httplib::SSLServer svr; + +svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { + res.set_content("Hello World!", "text/plain"); +}); + +svr.listen("0.0.0.0", 8080); +``` + +#### Client + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co"); + +// HTTPS +httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co"); + +auto res = cli.Get("/hi"); +res->status; +res->body; +``` + +SSL Support +----------- + +SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. + +NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0. + +NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`. + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// Server +httplib::SSLServer svr("./cert.pem", "./key.pem"); + +// Client +httplib::Client cli("https://localhost:1234"); // scheme + host +httplib::SSLClient cli("localhost:1234"); // host +httplib::SSLClient cli("localhost", 1234); // host, port + +// Use your CA bundle +cli.set_ca_cert_path("./ca-bundle.crt"); + +// Disable cert verification +cli.enable_server_certificate_verification(false); +``` + +NOTE: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. + +Server +------ + +```c++ +#include + +int main(void) +{ + using namespace httplib; + + Server svr; + + svr.Get("/hi", [](const Request& req, Response& res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { + auto numbers = req.matches[1]; + res.set_content(numbers, "text/plain"); + }); + + svr.Get("/body-header-param", [](const Request& req, Response& res) { + if (req.has_header("Content-Length")) { + auto val = req.get_header_value("Content-Length"); + } + if (req.has_param("key")) { + auto val = req.get_param_value("key"); + } + res.set_content(req.body, "text/plain"); + }); + + svr.Get("/stop", [&](const Request& req, Response& res) { + svr.stop(); + }); + + svr.listen("localhost", 1234); +} +``` + +`Post`, `Put`, `Delete` and `Options` methods are also supported. + +### Bind a socket to multiple interfaces and any available port + +```cpp +int port = svr.bind_to_any_port("0.0.0.0"); +svr.listen_after_bind(); +``` + +### Static File Server + +```cpp +// Mount / to ./www directory +auto ret = svr.set_mount_point("/", "./www"); +if (!ret) { + // The specified base directory doesn't exist... +} + +// Mount /public to ./www directory +ret = svr.set_mount_point("/public", "./www"); + +// Mount /public to ./www1 and ./www2 directories +ret = svr.set_mount_point("/public", "./www1"); // 1st order to search +ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search + +// Remove mount / +ret = svr.remove_mount_point("/"); + +// Remove mount /public +ret = svr.remove_mount_point("/public"); +``` + +```cpp +// User defined file extension and MIME type mappings +svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); +``` + +The followings are built-in mappings: + +| Extension | MIME Type | Extension | MIME Type | +| :--------- | :-------------------------- | :--------- | :-------------------------- | +| css | text/css | mpga | audio/mpeg | +| csv | text/csv | weba | audio/webm | +| txt | text/plain | wav | audio/wave | +| vtt | text/vtt | otf | font/otf | +| html, htm | text/html | ttf | font/ttf | +| apng | image/apng | woff | font/woff | +| avif | image/avif | woff2 | font/woff2 | +| bmp | image/bmp | 7z | application/x-7z-compressed | +| gif | image/gif | atom | application/atom+xml | +| png | image/png | pdf | application/pdf | +| svg | image/svg+xml | mjs, js | application/javascript | +| webp | image/webp | json | application/json | +| ico | image/x-icon | rss | application/rss+xml | +| tif | image/tiff | tar | application/x-tar | +| tiff | image/tiff | xhtml, xht | application/xhtml+xml | +| jpeg, jpg | image/jpeg | xslt | application/xslt+xml | +| mp4 | video/mp4 | xml | application/xml | +| mpeg | video/mpeg | gz | application/gzip | +| webm | video/webm | zip | application/zip | +| mp3 | audio/mp3 | wasm | application/wasm | + +### File request handler + +```cpp +// The handler is called right before the response is sent to a client +svr.set_file_request_handler([](const Request &req, Response &res) { + ... +}); +``` + +NOTE: These static file server methods are not thread-safe. + +### Logging + +```cpp +svr.set_logger([](const auto& req, const auto& res) { + your_logger(req, res); +}); +``` + +### Error handler + +```cpp +svr.set_error_handler([](const auto& req, auto& res) { + auto fmt = "

Error Status: %d

"; + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), fmt, res.status); + res.set_content(buf, "text/html"); +}); +``` + +### Exception handler +The exception handler gets called if a user routing handler throws an error. + +```cpp +svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { + auto fmt = "

Error 500

%s

"; + char buf[BUFSIZ]; + try { + std::rethrow_exception(ep); + } catch (std::exception &e) { + snprintf(buf, sizeof(buf), fmt, e.what()); + } catch (...) { // See the following NOTE + snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); + } + res.set_content(buf, "text/html"); + res.status = 500; +}); +``` + +NOTE: if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful! + +### Pre routing handler + +```cpp +svr.set_pre_routing_handler([](const auto& req, auto& res) { + if (req.path == "/hello") { + res.set_content("world", "text/html"); + return Server::HandlerResponse::Handled; + } + return Server::HandlerResponse::Unhandled; +}); +``` + +### Post routing handler + +```cpp +svr.set_post_routing_handler([](const auto& req, auto& res) { + res.set_header("ADDITIONAL_HEADER", "value"); +}); +``` + +### 'multipart/form-data' POST data + +```cpp +svr.Post("/multipart", [&](const auto& req, auto& res) { + auto size = req.files.size(); + auto ret = req.has_file("name1"); + const auto& file = req.get_file_value("name1"); + // file.filename; + // file.content_type; + // file.content; +}); +``` + +### Receive content with a content receiver + +```cpp +svr.Post("/content_receiver", + [&](const Request &req, Response &res, const ContentReader &content_reader) { + if (req.is_multipart_form_data()) { + // NOTE: `content_reader` is blocking until every form data field is read + MultipartFormDataItems files; + content_reader( + [&](const MultipartFormData &file) { + files.push_back(file); + return true; + }, + [&](const char *data, size_t data_length) { + files.back().content.append(data, data_length); + return true; + }); + } else { + std::string body; + content_reader([&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); + } + }); +``` + +### Send content with the content provider + +```cpp +const size_t DATA_CHUNK_SIZE = 4; + +svr.Get("/stream", [&](const Request &req, Response &res) { + auto data = new std::string("abcdefg"); + + res.set_content_provider( + data->size(), // Content length + "text/plain", // Content type + [data](size_t offset, size_t length, DataSink &sink) { + const auto &d = *data; + sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE)); + return true; // return 'false' if you want to cancel the process. + }, + [data](bool success) { delete data; }); +}); +``` + +Without content length: + +```cpp +svr.Get("/stream", [&](const Request &req, Response &res) { + res.set_content_provider( + "text/plain", // Content type + [&](size_t offset, DataSink &sink) { + if (/* there is still data */) { + std::vector data; + // prepare data... + sink.write(data.data(), data.size()); + } else { + sink.done(); // No more data + } + return true; // return 'false' if you want to cancel the process. + }); +}); +``` + +### Chunked transfer encoding + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done(); // No more data + return true; // return 'false' if you want to cancel the process. + } + ); +}); +``` + +With trailer: + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_header("Trailer", "Dummy1, Dummy2"); + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done_with_trailer({ + {"Dummy1", "DummyVal1"}, + {"Dummy2", "DummyVal2"} + }); + return true; + } + ); +}); +``` + +### 'Expect: 100-continue' handler + +By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header. + +```cpp +// Send a '417 Expectation Failed' response. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return 417; +}); +``` + +```cpp +// Send a final status without reading the message body. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return res.status = 401; +}); +``` + +### Keep-Alive connection + +```cpp +svr.set_keep_alive_max_count(2); // Default is 5 +svr.set_keep_alive_timeout(10); // Default is 5 +``` + +### Timeout + +```c++ +svr.set_read_timeout(5, 0); // 5 seconds +svr.set_write_timeout(5, 0); // 5 seconds +svr.set_idle_interval(0, 100000); // 100 milliseconds +``` + +### Set maximum payload length for reading a request body + +```c++ +svr.set_payload_max_length(1024 * 1024 * 512); // 512MB +``` + +### Server-Sent Events + +Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc). + +### Default thread pool support + +`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. + +If you want to set the thread count at runtime, there is no convenient way... But here is how. + +```cpp +svr.new_task_queue = [] { return new ThreadPool(12); }; +``` + +### Override the default thread pool with yours + +You can supply your own thread pool implementation according to your need. + +```cpp +class YourThreadPoolTaskQueue : public TaskQueue { +public: + YourThreadPoolTaskQueue(size_t n) { + pool_.start_with_thread_count(n); + } + + virtual void enqueue(std::function fn) override { + pool_.enqueue(fn); + } + + virtual void shutdown() override { + pool_.shutdown_gracefully(); + } + +private: + YourThreadPool pool_; +}; + +svr.new_task_queue = [] { + return new YourThreadPoolTaskQueue(12); +}; +``` + +Client +------ + +```c++ +#include +#include + +int main(void) +{ + httplib::Client cli("localhost", 1234); + + if (auto res = cli.Get("/hi")) { + if (res->status == 200) { + std::cout << res->body << std::endl; + } + } else { + auto err = res.error(); + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } +} +``` + +NOTE: Constructor with scheme-host-port string is now supported! + +```c++ +httplib::Client cli("localhost"); +httplib::Client cli("localhost:8080"); +httplib::Client cli("http://localhost"); +httplib::Client cli("http://localhost:8080"); +httplib::Client cli("https://localhost"); +httplib::SSLClient cli("localhost"); +``` + +### Error code + +Here is the list of errors from `Result::error()`. + +```c++ +enum Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, +}; +``` + +### GET with HTTP headers + +```c++ +httplib::Headers headers = { + { "Accept-Encoding", "gzip, deflate" } +}; +auto res = cli.Get("/hi", headers); +``` +or +```c++ +auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}}); +``` +or +```c++ +cli.set_default_headers({ + { "Accept-Encoding", "gzip, deflate" } +}); +auto res = cli.Get("/hi"); +``` + +### POST + +```c++ +res = cli.Post("/post", "text", "text/plain"); +res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded"); +``` + +### POST with parameters + +```c++ +httplib::Params params; +params.emplace("name", "john"); +params.emplace("note", "coder"); + +auto res = cli.Post("/post", params); +``` + or + +```c++ +httplib::Params params{ + { "name", "john" }, + { "note", "coder" } +}; + +auto res = cli.Post("/post", params); +``` + +### POST with Multipart Form Data + +```c++ +httplib::MultipartFormDataItems items = { + { "text1", "text default", "", "" }, + { "text2", "aωb", "", "" }, + { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, + { "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" }, + { "file3", "", "", "application/octet-stream" }, +}; + +auto res = cli.Post("/multipart", items); +``` + +### PUT + +```c++ +res = cli.Put("/resource/foo", "text", "text/plain"); +``` + +### DELETE + +```c++ +res = cli.Delete("/resource/foo"); +``` + +### OPTIONS + +```c++ +res = cli.Options("*"); +res = cli.Options("/resource/foo"); +``` + +### Timeout + +```c++ +cli.set_connection_timeout(0, 300000); // 300 milliseconds +cli.set_read_timeout(5, 0); // 5 seconds +cli.set_write_timeout(5, 0); // 5 seconds +``` + +### Receive content with a content receiver + +```c++ +std::string body; + +auto res = cli.Get("/large-data", + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); +``` + +```cpp +std::string body; + +auto res = cli.Get( + "/stream", Headers(), + [&](const Response &response) { + EXPECT_EQ(200, response.status); + return true; // return 'false' if you want to cancel the request. + }, + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; // return 'false' if you want to cancel the request. + }); +``` + +### Send content with a content provider + +```cpp +std::string body = ...; + +auto res = cli.Post( + "/stream", body.size(), + [](size_t offset, size_t length, DataSink &sink) { + sink.write(body.data() + offset, length); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### Chunked transfer encoding + +```cpp +auto res = cli.Post( + "/stream", + [](size_t offset, DataSink &sink) { + sink.os << "chunked data 1"; + sink.os << "chunked data 2"; + sink.os << "chunked data 3"; + sink.done(); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### With Progress Callback + +```cpp +httplib::Client client(url, port); + +// prints: 0 / 000 bytes => 50% complete +auto res = cli.Get("/", [](uint64_t len, uint64_t total) { + printf("%lld / %lld bytes => %d%% complete\n", + len, total, + (int)(len*100/total)); + return true; // return 'false' if you want to cancel the request. +} +); +``` + +![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) + +### Authentication + +```cpp +// Basic Authentication +cli.set_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_bearer_token_auth("token"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Proxy server support + +```cpp +cli.set_proxy("host", port); + +// Basic Authentication +cli.set_proxy_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_proxy_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_proxy_bearer_token_auth("pass"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Range + +```cpp +httplib::Client cli("httpbin.org"); + +auto res = cli.Get("/range/32", { + httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10' +}); +// res->status should be 206. +// res->body should be "bcdefghijk". +``` + +```cpp +httplib::make_range_header({{1, 10}, {20, -1}}) // 'Range: bytes=1-10, 20-' +httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599' +httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1' +``` + +### Keep-Alive connection + +```cpp +httplib::Client cli("localhost", 1234); + +cli.Get("/hello"); // with "Connection: close" + +cli.set_keep_alive(true); +cli.Get("/world"); + +cli.set_keep_alive(false); +cli.Get("/last-request"); // with "Connection: close" +``` + +### Redirect + +```cpp +httplib::Client cli("yahoo.com"); + +auto res = cli.Get("/"); +res->status; // 301 + +cli.set_follow_location(true); +res = cli.Get("/"); +res->status; // 200 +``` + +### Use a specific network interface + +NOTE: This feature is not available on Windows, yet. + +```cpp +cli.set_interface("eth0"); // Interface name, IP address or host name +``` + +Compression +----------- + +The server can apply compression to the following MIME type contents: + + * all text types except text/event-stream + * image/svg+xml + * application/javascript + * application/json + * application/xml + * application/xhtml+xml + +### Zlib Support + +'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked. + +### Brotli Support + +Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/google/brotli for more detail. + +### Compress request body on client + +```c++ +cli.set_compress(true); +res = cli.Post("/resource/foo", "...", "text/plain"); +``` + +### Compress response body on client + +```c++ +cli.set_decompress(false); +res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); +res->body; // Compressed data +``` + +Use `poll` instead of `select` +------------------------------ + +`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`. + + +Split httplib.h into .h and .cc +------------------------------- + +```console +$ ./split.py -h +usage: split.py [-h] [-e EXTENSION] [-o OUT] + +This script splits httplib.h into .h and .cc parts. + +optional arguments: + -h, --help show this help message and exit + -e EXTENSION, --extension EXTENSION + extension of the implementation file (default: cc) + -o OUT, --out OUT where to write the files (default: out) + +$ ./split.py +Wrote out/httplib.h and out/httplib.cc +``` + +NOTE +---- + +### g++ + +g++ 4.8 and below cannot build this library since `` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). + +### Windows + +Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand. + +```cpp +#include +#include +``` + +```cpp +#define WIN32_LEAN_AND_MEAN +#include +#include +``` + +NOTE: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. + +NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported. + +License +------- + +MIT license (© 2023 Yuji Hirose) + +Special Thanks To +----------------- + +[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy! diff --git a/VideoProsessing/NetraLib/include/encrypt.hpp b/VideoProsessing/NetraLib/include/encrypt.hpp new file mode 100644 index 0000000..42ea793 --- /dev/null +++ b/VideoProsessing/NetraLib/include/encrypt.hpp @@ -0,0 +1,14 @@ +#pragma once + +/* +主要是用于各种加密 +*/ + +#include "QCL_Include.hpp" + +using namespace std; + +namespace encrypt +{ + string MD5(const string &info); +} \ No newline at end of file diff --git a/VideoProsessing/NetraLib/include/httplib.h b/VideoProsessing/NetraLib/include/httplib.h new file mode 100644 index 0000000..1bdef69 --- /dev/null +++ b/VideoProsessing/NetraLib/include/httplib.h @@ -0,0 +1,8794 @@ +// +// httplib.h +// +// Copyright (c) 2023 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.12.2" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif // strcasecmp + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#ifndef _AIX +#include +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x1010100fL +#error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +struct ci { + bool operator()(const std::string &s1, const std::string &s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); + } +}; + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +} // namespace detail + +using Headers = std::multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + Params params; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + void set_redirect(const std::string &url, int status = 302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + template + ssize_t write_format(const char *fmt, const Args &...args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual void enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool : public TaskQueue { +public: + explicit ThreadPool(size_t n) : shutdown_(false) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + void enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = std::move(pool_.jobs_.front()); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_file_request_handler(Handler handler); + + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = std::vector>; + using HandlersForContentReader = + std::vector>; + + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, const Handlers &handlers); + bool + dispatch_request_for_content_reader(Request &req, Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers); + + bool parse_request_line(const char *s, Request &req); + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); + bool write_response(Stream &strm, bool close_connection, const Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + + virtual bool process_and_close_socket(socket_t sock); + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + + std::atomic is_running_{false}; + std::atomic done_{false}; + std::map file_extension_and_mimetype_map_; + Handler file_request_handler_; + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Logger logger_; + Expect100ContinueHandler expect_100_continue_handler_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(const Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; + template + T get_request_header_value(const std::string &key, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_; + Headers request_headers_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); + + void copy_settings(const ClientImpl &rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif + + Logger logger_; + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + Client(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +template +inline T get_header_value(const Headers & /*headers*/, + const std::string & /*key*/, size_t /*id*/ = 0, + uint64_t /*def*/ = 0) {} + +template <> +inline uint64_t get_header_value(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +template +inline T Request::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline T Response::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf{}; + + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +template +inline T Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, 0); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(Ranges ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void split(const char *b, const char *e, char d, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + virtual ~nocompressor() = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor(); + ~gzip_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + int v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + const char *charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = (code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + int val = 0; + int valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_file(const std::string &path) { +#ifdef _WIN32 + return _access_s(path.c_str(), 0) == 0; +#else + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +#endif +} + +inline bool is_dir(const std::string &path) { + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + int val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + int val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + size_t i = 0; + size_t beg = 0; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { break; } + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } +} + +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = false; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024 * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + +inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; + } + } +} + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET; + + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + std::copy(host.begin(), host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + + fcntl(sock, F_SETFD, FD_CLOEXEC); + if (socket_options) { socket_options(sock); } + + if (!bind_or_connect(sock, hints)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + if (sock == INVALID_SOCKET) { continue; } + +#ifndef _WIN32 + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + int yes = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), + sizeof(yes)); + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + int no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), + sizeof(no)); + } + + // bind or connect + if (bind_or_connect(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + freeaddrinfo(result); + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + freeifaddrs(ifap); + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + freeifaddrs(ifap); + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + freeifaddrs(ifap); + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if.c_str())) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { return false; } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline const char * +find_content_type(const std::string &path, + const std::map &user_data) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second.c_str(); } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return nullptr; + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline const char *status_message(int status) { + switch (status) { + case 100: return "Continue"; + case 101: return "Switching Protocol"; + case 102: return "Processing"; + case 103: return "Early Hints"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choice"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "unused"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Too Early"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + + default: + case 500: return "Internal Server Error"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + default: + return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + int ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + int ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + auto prev_avail_in = strm_.avail_in; + + ret = inflate(&strm_, Z_NO_FLUSH); + + if (prev_avail_in - strm_.avail_in == 0) { return false; } + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) return false; + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +inline bool compare_case_ignore(const std::string &a, const std::string &b) { + if (a.size() != b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + auto key = std::string(beg, key_end); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + fn(std::move(key), std::move(val)); + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + } else { + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; + } +#else + } else { + continue; // Skip invalid line. + } +#endif + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n < 0) { + return false; + } else if (n == 0) { + return true; + } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n")) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n")) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), + "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = 500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value(x.headers, "Content-Length"); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { status = exceed_payload_max_length ? 413 : 400; } + return ret; + }); +} // namespace detail + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + res.location = location; + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = content_type.substr(beg, end - beg); + if (boundary.length() >= 2 && boundary.front() == '"' && + boundary.back() == '"') { + boundary = boundary.substr(1, boundary.size() - 2); + } + return !boundary.empty(); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); + std::smatch m; + if (std::regex_match(s, m, re_first_range)) { + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); + bool all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) return; + static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); + std::cmatch cm; + if (std::regex_match(b, e, cm, re_another_range)) { + ssize_t first = -1; + if (!cm.str(1).empty()) { + first = static_cast(std::stoll(cm.str(1))); + } + + ssize_t last = -1; + if (!cm.str(2).empty()) { + last = static_cast(std::stoll(cm.str(2))); + } + + if (first != -1 && last != -1 && first > last) { + all_valid_ranges = false; + return; + } + ranges.emplace_back(std::make_pair(first, last)); + } + }); + return all_valid_ranges; + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + // TODO: support 'filename*' + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~", + std::regex_constants::icase); + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + static const std::string header_name = "content-type:"; + const auto header = buf_head(pos); + if (start_with_case_ignore(header, header_name)) { + file_.content_type = trim_copy(header.substr(header_name.size())); + } else { + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + file_.name = m[1]; + file_.filename = m[2]; + } else { + is_valid_ = false; + return false; + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_crlf_.size() > buf_size()) { return true; } + if (buf_start_with(dash_crlf_)) { + buf_erase(dash_crlf_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + const std::string dash_crlf_ = "--\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string to_lower(const char *beg, const char *end) { + std::string out; + auto it = beg; + while (it != end) { + out += static_cast(::tolower(*it)); + it++; + } + return out; +} + +inline std::string make_multipart_data_boundary() { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); + + std::string result = "--cpp-httplib-multipart-data-"; + + for (auto i = 0; i < 16; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + + return result; +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) body += serialize_multipart_formdata_finish(boundary); + + return body; +} + +inline std::pair +get_range_offset_and_length(const Request &req, size_t content_length, + size_t index) { + auto r = req.ranges[index]; + + if (r.first == -1 && r.second == -1) { + return std::make_pair(0, content_length); + } + + auto slen = static_cast(content_length); + + if (r.first == -1) { + r.first = (std::max)(static_cast(0), slen - r.second); + r.second = slen - 1; + } + + if (r.second == -1) { r.second = slen - 1; } + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field(size_t offset, size_t length, + size_t content_length) { + std::string field = "bytes "; + field += std::to_string(offset); + field += "-"; + field += std::to_string(offset + length - 1); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + SToken stoken, CToken ctoken, + Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offsets = get_range_offset_and_length(req, res.body.size(), i); + auto offset = offsets.first; + auto length = offsets.second; + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset, length, res.body.size())); + ctoken("\r\n"); + ctoken("\r\n"); + if (!content(offset, length)) { return false; } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--\r\n"); + + return true; +} + +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; + }); +} + +inline size_t +get_multipart_ranges_data_length(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool write_multipart_ranges_data(Stream &strm, const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + const T &is_shutting_down) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline std::pair +get_range_offset_and_length(const Request &req, const Response &res, + size_t index) { + auto r = req.ranges[index]; + + if (r.second == -1) { + r.second = static_cast(res.content_length_) - 1; + } + + return std::make_pair(r.first, r.second - r.first + 1); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << (unsigned int)hash[i]; + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (int i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + auto m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 +inline std::string random_string(size_t length) { + auto randchar = []() -> char { + const char charset[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[static_cast(std::rand()) % max_index]; + }; + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + int dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } + + freeaddrinfo(result); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair make_range_header(Ranges ranges) { + std::string field = "bytes="; + auto i = 0; + for (auto r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const std::string &key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = 302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = true; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() {} + +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() {} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + if (detail::is_dir(dir)) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(HandlerWithResponse handler) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + if (bind_internal(host, port, socket_flags) < 0) return false; + return true; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + return bind_internal(host, 0, socket_flags); +} + +inline bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + auto se = detail::scope_exit([&]() { done_ = true; }); + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } +} + +inline bool Server::parse_request_line(const char *s, Request &req) { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + size_t count = 0; + + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), req.params); + } + break; + } + default: break; + } + count++; + }); + + if (count > 2) { return false; } + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); + } + + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + res.set_header("Content-Type", "text/plain"); + } + + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { + return false; + } + + if (!detail::write_headers(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + res.content_provider_success_ = false; + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = 413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); + if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } + res.status = req.has_header("Range") ? 206 : 200; + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + task_queue->enqueue([this, sock]() { process_and_close_socket(sock); }); + } + + task_queue->shutdown(); + } + + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + bool is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = 400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { + if (req.ranges.size() > 1) { + boundary = detail::make_multipart_data_boundary(); + + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + res.headers.emplace("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty()) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length(req, res, boundary, + content_type); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty()) { + ; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.body.size(), 0); + auto offset = offsets.first; + auto length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.body.size()); + res.set_header("Content-Range", content_range); + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } + } else { + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + Response res; + + res.version = "HTTP/1.1"; + + for (const auto &header : default_headers_) { + if (res.headers.find(header.first) == res.headers.end()) { + res.headers.insert(header); + } + } + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = 400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = 416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + auto status = 100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case 100: + case 417: + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + detail::status_message(status)); + break; + default: return write_response(strm, close_connection, req, res); + } + } + + // Rounting + bool routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + + if (routed) { + if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(host), port_(port), + host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) ip = it->second; + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == 100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == 407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.headers.emplace("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.headers.emplace("User-Agent", agent); + } +#endif + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.headers.emplace("Content-Length", length); + } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.headers.emplace("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.headers.emplace("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.headers.emplace("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + detail::write_headers(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.headers.emplace("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + ; + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by + // handle_request which is only called by send, which locks the request + // mutex during the process. It would be a bug to call it from a different + // thread since it's a thread-safety issue to do these things to the socket + // if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + size_t cur_item = 0, cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && items.size()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + bool has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + return false; + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, response_handler, + content_receiver, progress); +} + +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + int res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() {} + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + // add default password callback before opening encrypted private key + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata(ctx_, + (char *)private_key_password); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response res2; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, res2, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (res2.status == 407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res2, auth, true)) { + Response res3; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, res3, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } else { + res = res2; + return false; + } + } + + return true; +} + +inline bool SSLClient::load_certs() { + bool ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl2) { + SSL_set_tlsext_host_name(ssl2, host_.c_str()); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6; + struct in_addr addr; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); + auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(std::string(b, e)); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() {} + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); +} + +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline socket_t Client::socket() const { return cli_->socket(); } + +inline void Client::stop() { cli_->stop(); } + +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + +inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/VideoProsessing/NetraLib/src/NetRequest.cpp b/VideoProsessing/NetraLib/src/NetRequest.cpp new file mode 100644 index 0000000..f3aa541 --- /dev/null +++ b/VideoProsessing/NetraLib/src/NetRequest.cpp @@ -0,0 +1,635 @@ +#include "NetRequest.hpp" + +#include +#include +#include +#include +#include +#include + +namespace ntq +{ + namespace + { + static std::string joinPath(const std::string &base, const std::string &path) + { + if (base.empty()) return path.empty() || path[0] == '/' ? path : std::string("/") + path; + if (path.empty()) return base[0] == '/' ? base : std::string("/") + base; + bool base_has = base.front() == '/'; + bool base_end = base.back() == '/'; + bool path_has = path.front() == '/'; + std::string b = base_has ? base : std::string("/") + base; + if (base_end && path_has) return b + path.substr(1); + if (!base_end && !path_has) return b + "/" + path; + return b + path; + } + + static std::string paramsToQuery(const httplib::Params ¶ms) + { + if (params.empty()) return {}; + std::string s; + bool first = true; + for (auto &kv : params) + { + if (!first) s += '&'; + first = false; + s += kv.first; + s += '='; + s += kv.second; + } + return s; + } + + static httplib::Headers mergeHeaders(const httplib::Headers &a, const httplib::Headers &b) + { + httplib::Headers h = a; + for (auto &kv : b) + { + // 覆盖同名 header:先删再插 + h.erase(kv.first); + h.emplace(kv.first, kv.second); + } + return h; + } + } + + class ConcurrencyGate + { + public: + explicit ConcurrencyGate(size_t limit) : limit_(limit), active_(0) {} + + void set_limit(size_t limit) + { + std::lock_guard lk(mtx_); + limit_ = limit > 0 ? limit : 1; + cv_.notify_all(); + } + + struct Guard + { + ConcurrencyGate &g; + explicit Guard(ConcurrencyGate &gate) : g(gate) { g.enter(); } + ~Guard() { g.leave(); } + }; + + private: + friend struct Guard; + void enter() + { + std::unique_lock lk(mtx_); + cv_.wait(lk, [&]{ return active_ < limit_; }); + ++active_; + } + void leave() + { + std::lock_guard lk(mtx_); + if (active_ > 0) --active_; + cv_.notify_one(); + } + + size_t limit_; + size_t active_; + std::mutex mtx_; + std::condition_variable cv_; + }; + + struct NetRequest::Impl + { + RequestOptions opts; + LogCallback logger; + Stats stats; + + // 并发控制 + ConcurrencyGate gate{4}; + + // 缓存 + struct CacheEntry + { + HttpResponse resp; + std::chrono::steady_clock::time_point expiry; + }; + bool cache_enabled = false; + std::chrono::milliseconds cache_ttl{0}; + std::unordered_map cache; + std::mutex cache_mtx; + + void log(const std::string &msg) + { + if (logger) logger(msg); + } + + template + void apply_client_options(ClientT &cli) + { + const time_t c_sec = static_cast(opts.connect_timeout_ms / 1000); + const time_t c_usec = static_cast((opts.connect_timeout_ms % 1000) * 1000); + const time_t r_sec = static_cast(opts.read_timeout_ms / 1000); + const time_t r_usec = static_cast((opts.read_timeout_ms % 1000) * 1000); + const time_t w_sec = static_cast(opts.write_timeout_ms / 1000); + const time_t w_usec = static_cast((opts.write_timeout_ms % 1000) * 1000); + cli.set_connection_timeout(c_sec, c_usec); + cli.set_read_timeout(r_sec, r_usec); + cli.set_write_timeout(w_sec, w_usec); + cli.set_keep_alive(opts.keep_alive); + } + + std::string build_full_path(const std::string &path) const + { + return joinPath(opts.base_path, path); + } + + std::string cache_key(const std::string &path, const httplib::Params ¶ms, const httplib::Headers &headers) + { + std::ostringstream oss; + oss << opts.scheme << "://" << opts.host << ':' << opts.port << build_full_path(path); + if (!params.empty()) oss << '?' << paramsToQuery(params); + for (auto &kv : headers) oss << '|' << kv.first << '=' << kv.second; + return oss.str(); + } + + void record_latency(double ms) + { + stats.last_latency_ms = ms; + const double alpha = 0.2; + if (stats.avg_latency_ms <= 0.0) stats.avg_latency_ms = ms; + else stats.avg_latency_ms = alpha * ms + (1.0 - alpha) * stats.avg_latency_ms; + } + + static ErrorCode map_error() + { + // 简化:无法区分具体错误码,统一归为 Network + return ErrorCode::Network; + } + }; + + NetRequest::NetRequest(const RequestOptions &options) + : impl_(new Impl) + { + impl_->opts = options; + if (impl_->opts.scheme == "https" && impl_->opts.port == 80) impl_->opts.port = 443; + if (impl_->opts.scheme == "http" && impl_->opts.port == 0) impl_->opts.port = 80; + } + + NetRequest::~NetRequest() + { + delete impl_; + } + + void NetRequest::setLogger(LogCallback logger) + { + impl_->logger = std::move(logger); + } + + void NetRequest::setMaxConcurrentRequests(size_t n) + { + impl_->gate.set_limit(n > 0 ? n : 1); + } + + void NetRequest::enableCache(std::chrono::milliseconds ttl) + { + impl_->cache_enabled = true; + impl_->cache_ttl = ttl.count() > 0 ? ttl : std::chrono::milliseconds(1000); + } + + void NetRequest::disableCache() + { + impl_->cache_enabled = false; + std::lock_guard lk(impl_->cache_mtx); + impl_->cache.clear(); + } + + ntq::optional NetRequest::Get(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, mergeHeaders(impl_->opts.default_headers, headers)); + std::lock_guard lk(impl_->cache_mtx); + auto it = impl_->cache.find(key); + if (it != impl_->cache.end() && std::chrono::steady_clock::now() < it->second.expiry) + { + if (err) *err = ErrorCode::None; + auto resp = it->second.resp; + resp.from_cache = true; + return resp; + } + } + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + impl_->log("HTTPS requested but OpenSSL is not enabled; falling back to error."); + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!result.has_value()) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, merged_headers); + std::lock_guard lk(impl_->cache_mtx); + impl_->cache[key] = Impl::CacheEntry{*result, std::chrono::steady_clock::now() + impl_->cache_ttl}; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + std::future> NetRequest::GetAsync(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, query, headers, err]() mutable { + ErrorCode local; + auto r = Get(path, query, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, json, headers, err]() mutable { + ErrorCode local; + auto r = PostJson(path, json, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, form, headers, err]() mutable { + ErrorCode local; + auto r = PostForm(path, form, headers, &local); + if (err) *err = local; + return r; + }); + } + + bool NetRequest::DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers, + bool resume, + size_t /*chunk_size*/, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + std::ios_base::openmode mode = std::ios::binary | std::ios::out; + size_t offset = 0; + if (resume) + { + std::ifstream in(local_file, std::ios::binary | std::ios::ate); + if (in) + { + offset = static_cast(in.tellg()); + } + mode |= std::ios::app; + } + else + { + mode |= std::ios::trunc; + } + + std::ofstream out(local_file, mode); + if (!out) + { + if (err) *err = ErrorCode::IOError; + impl_->stats.total_errors++; + return false; + } + + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + if (resume && offset > 0) + { + merged_headers.emplace("Range", "bytes=" + std::to_string(offset) + "-"); + } + + const auto full_path = impl_->build_full_path(path); + int status_code = 0; + ErrorCode local_err = ErrorCode::None; + + auto content_receiver = [&](const char *data, size_t data_length) { + out.write(data, static_cast(data_length)); + return static_cast(out); + }; + + bool ok = false; + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } + } + + out.close(); + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!ok) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return false; + } + if (err) *err = ErrorCode::None; + return true; + } + + NetRequest::Stats NetRequest::getStats() const + { + return impl_->stats; + } + + // ------------------------- Quick helpers ------------------------- + namespace { + struct ParsedURL { + std::string scheme; + std::string host; + int port = 0; + std::string path_and_query; + bool ok = false; + }; + + static ParsedURL parse_url(const std::string &url) + { + ParsedURL p; p.ok = false; + // very small parser: scheme://host[:port]/path[?query] + auto pos_scheme = url.find("://"); + if (pos_scheme == std::string::npos) return p; + p.scheme = url.substr(0, pos_scheme); + size_t pos_host = pos_scheme + 3; + + size_t pos_path = url.find('/', pos_host); + std::string hostport = pos_path == std::string::npos ? url.substr(pos_host) + : url.substr(pos_host, pos_path - pos_host); + auto pos_colon = hostport.find(':'); + if (pos_colon == std::string::npos) { + p.host = hostport; + p.port = (p.scheme == "https") ? 443 : 80; + } else { + p.host = hostport.substr(0, pos_colon); + std::string port_str = hostport.substr(pos_colon + 1); + p.port = port_str.empty() ? ((p.scheme == "https") ? 443 : 80) : std::atoi(port_str.c_str()); + } + p.path_and_query = (pos_path == std::string::npos) ? "/" : url.substr(pos_path); + p.ok = !p.host.empty(); + return p; + } + } + + ntq::optional NetRequest::QuickGet(const std::string &url, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.Get(p.path_and_query, {}, headers, err); + } + + ntq::optional NetRequest::QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostJson(p.path_and_query, json, headers, err); + } + + ntq::optional NetRequest::QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostForm(p.path_and_query, form, headers, err); + } +} diff --git a/VideoProsessing/NetraLib/src/Netra.cpp b/VideoProsessing/NetraLib/src/Netra.cpp new file mode 100644 index 0000000..a67282c --- /dev/null +++ b/VideoProsessing/NetraLib/src/Netra.cpp @@ -0,0 +1,693 @@ +#include "Netra.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TcpServer::TcpServer(int port) + : port_(port), running_(false), serverSock_(-1) {} + + /** + * @brief 析构函数中调用stop()确保服务器资源被释放 + */ + TcpServer::~TcpServer() + { + stop(); + } + + /** + * @brief 启动服务器: + * 1. 创建监听socket(TCP) + * 2. 绑定端口 + * 3. 监听端口 + * 4. 启动监听线程acceptThread_ + * + * @return 成功返回true,失败返回false + */ + bool TcpServer::start() + { + // 创建socket + serverSock_ = socket(AF_INET, SOCK_STREAM, 0); + if (serverSock_ < 0) + { + std::cerr << "Socket 创建失败\n"; + return false; + } + + // 设置socket地址结构 + sockaddr_in serverAddr; + std::memset(&serverAddr, 0, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port_); // 端口转网络字节序 + serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡IP + + // 允许端口重用,防止服务器异常关闭后端口被占用 + int opt = 1; + setsockopt(serverSock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + // 绑定端口 + if (bind(serverSock_, (sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) + { + std::cerr << "绑定失败\n"; + return false; + } + + // 开始监听,最大等待连接数为5 + if (listen(serverSock_, 5) < 0) + { + std::cerr << "监听失败\n"; + return false; + } + + // 设置运行标志为true + running_ = true; + + // 启动专门接受客户端连接的线程 + acceptThread_ = std::thread(&TcpServer::acceptClients, this); + + std::cout << "服务器启动,监听端口:" << port_ << std::endl; + return true; + } + + /** + * @brief 停止服务器: + * 1. 设置运行标志为false,通知线程退出 + * 2. 关闭监听socket + * 3. 关闭所有客户端socket,清理客户端列表 + * 4. 等待所有线程退出 + */ + void TcpServer::stop() + { + running_ = false; + + if (serverSock_ >= 0) + { + close(serverSock_); + serverSock_ = -1; + } + + { + // 线程安全关闭所有客户端socket + std::lock_guard lock(clientsMutex_); + for (int sock : clientSockets_) + { + close(sock); + } + clientSockets_.clear(); + } + + // 等待监听线程退出 + if (acceptThread_.joinable()) + acceptThread_.join(); + + // 等待所有客户端处理线程退出 + for (auto &t : clientThreads_) + { + if (t.joinable()) + t.join(); + } + + std::cout << "服务器已停止\n"; + } + + /** + * @brief acceptClients函数循环监听客户端连接请求 + * 每当accept成功: + * 1. 打印客户端IP和Socket信息 + * 2. 线程安全地将客户端Socket加入clientSockets_列表 + * 3. 创建新线程调用handleClient处理该客户端收发 + */ + void TcpServer::acceptClients() + { + while (running_) + { + sockaddr_in clientAddr; + socklen_t clientLen = sizeof(clientAddr); + int clientSock = accept(serverSock_, (sockaddr *)&clientAddr, &clientLen); + if (clientSock < 0) + { + if (running_) + std::cerr << "接受连接失败\n"; + continue; + } + + // 将客户端IP转换成字符串格式打印 + char clientIP[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(clientAddr.sin_addr), clientIP, INET_ADDRSTRLEN); + std::cout << "客户端连接,IP: " << clientIP << ", Socket: " << clientSock << std::endl; + + { + // 加锁保护共享的clientSockets_容器 + std::lock_guard lock(clientsMutex_); + clientSockets_.push_back(clientSock); + } + } + } + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端socket + * @param message 发送消息内容 + */ + void TcpServer::sendToClient(int clientSock, const std::string &message) + { + send(clientSock, message.c_str(), message.size(), 0); + } + + /** + * @brief 单次接收指定客户端数据 + * @param clientSock 客户端socket + */ + std::string TcpServer::receiveFromClient(int clientSock, bool flag) + { + char buffer[1024]; + std::memset(buffer, 0, sizeof(buffer)); + + int flags = flag ? 0 : MSG_DONTWAIT; + ssize_t bytesReceived = recv(clientSock, buffer, sizeof(buffer) - 1, flags); + + if (bytesReceived <= 0) + return {}; + + return std::string(buffer, bytesReceived); + } + + /** + * @brief 获取当前所有客户端Socket副本(线程安全) + * @return 包含所有客户端socket的vector副本 + */ + std::vector TcpServer::getClientSockets() + { + std::lock_guard lock(clientsMutex_); + return clientSockets_; + } + + void TcpServer::removeClient(int clientSock) + { + std::lock_guard lock(clientsMutex_); + for (auto it = clientSockets_.begin(); it != clientSockets_.end(); ++it) + { + if (*it == clientSock) + { + close(*it); + clientSockets_.erase(it); + break; + } + } + } + + bool TcpServer::isClientDisconnected(int clientSock) + { + char tmp; + ssize_t n = recv(clientSock, &tmp, 1, MSG_PEEK | MSG_DONTWAIT); + if (n == 0) + return true; // 对端有序关闭 + if (n < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return false; // 只是暂时无数据 + return true; // 其它错误视为断开 + } + return false; // 有数据可读 + } + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *TcpServer::getClientIPAndPort(int clientSock) + { + struct sockaddr_in addr; + socklen_t addr_size = sizeof(addr); + + // 获取客户端地址信息 + if (getpeername(clientSock, (struct sockaddr *)&addr, &addr_size) == -1) + { + perror("getpeername failed"); + return NULL; + } + + // 分配内存存储结果(格式: "IP:PORT") + char *result = (char *)malloc(INET_ADDRSTRLEN + 10); + if (!result) + return NULL; + + // 转换IP和端口 + char *ip = inet_ntoa(addr.sin_addr); + unsigned short port = ntohs(addr.sin_port); + + snprintf(result, INET_ADDRSTRLEN + 10, "%s:%d", ip, port); + return result; + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + WriteFile::WriteFile(const std::string &filePath) + : filePath_(filePath) {} + + /** + * @brief 覆盖写文本(线程安全) + */ + bool WriteFile::overwriteText(const std::string &content) + { + std::lock_guard lock(writeMutex_); // 加锁 + return writeToFile(content, std::ios::out | std::ios::trunc); + } + + /** + * @brief 追加写文本(线程安全) + */ + bool WriteFile::appendText(const std::string &content) + { + std::lock_guard lock(writeMutex_); + return writeToFile(content, std::ios::out | std::ios::app); + } + + /** + * @brief 覆盖写二进制(线程安全) + */ + bool WriteFile::overwriteBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::trunc | std::ios::binary); + } + + /** + * @brief 追加写二进制(线程安全) + */ + bool WriteFile::appendBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::app | std::ios::binary); + } + + /** + * @brief 通用文本写入(私有) + */ + bool WriteFile::writeToFile(const std::string &content, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file << content; + file.close(); + return true; + } + + /** + * @brief 通用二进制写入(私有) + */ + bool WriteFile::writeBinary(const std::vector &data, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file.write(data.data(), data.size()); + file.close(); + return true; + } + + size_t WriteFile::countBytesPattern(const std::string &pattern, bool includePattern) + { + std::lock_guard lock(writeMutex_); + + if (pattern.empty()) + return 0; + + std::ifstream file(filePath_, std::ios::binary); + if (!file.is_open()) + return 0; + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file.read(chunk, chunkSize) || file.gcount() > 0) + { + size_t bytesRead = file.gcount(); + buffer.append(chunk, bytesRead); + + size_t pos = buffer.find(pattern); + if (pos != std::string::npos) + { + size_t absolutePos = totalRead + pos; // 关键:加上 totalRead + return includePattern ? (absolutePos + pattern.size()) : absolutePos; + } + + if (buffer.size() > pattern.size()) + buffer.erase(0, buffer.size() - pattern.size()); + + totalRead += bytesRead; // 读完后再累计 + } + + return 0; + } + + bool WriteFile::writeAfterPatternOrAppend(const std::string &pattern, const std::string &content) + { + std::lock_guard lock(writeMutex_); + + // 读取整个文件 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + size_t pos = fileData.find(pattern); + if (pos != std::string::npos) + { + // 模式存在,插入位置在模式结尾 + pos += pattern.size(); + + // 删除模式后所有内容 + if (pos < fileData.size()) + fileData.erase(pos); + + // 插入新内容 + fileData.insert(pos, content); + } + else + { + // 模式不存在,直接追加到文件末尾 + if (!fileData.empty() && fileData.back() != '\n') + fileData += '\n'; // 保证换行 + fileData += content; + } + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::overwriteAtPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos >= fileData.size()) + return false; // pos 超过文件范围,无法覆盖 + + // 生成要覆盖的实际数据块 + std::string overwriteBlock; + if (content.size() >= length) + { + overwriteBlock = content.substr(0, length); + } + else + { + overwriteBlock = content; + overwriteBlock.append(length - content.size(), '\0'); // 补齐 + } + + // 计算实际可写范围 + size_t maxWritable = std::min(length, fileData.size() - pos); + + // 覆盖 + fileData.replace(pos, maxWritable, overwriteBlock.substr(0, maxWritable)); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::insertAfterPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos > fileData.size()) + pos = fileData.size(); // 如果 pos 超出范围,就视为文件末尾 + + // 生成要插入的实际数据块 + std::string insertBlock; + if (content.size() >= length) + { + insertBlock = content.substr(0, length); // 只取前 length 个字节 + } + else + { + insertBlock = content; // 全部内容 + insertBlock.append(length - content.size(), '\0'); // 补足空字节 + } + + // 插入到 pos 后面 + fileData.insert(pos + 1, insertBlock); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ReadFile::ReadFile(const std::string &filename) : filename_(filename) {} + + ReadFile::~ReadFile() + { + std::lock_guard lock(mtx_); + Close(); + } + + bool ReadFile::Open() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + file_.close(); + file_.open(filename_, std::ios::in | std::ios::binary); + return file_.is_open(); + } + + void ReadFile::Close() + { + if (file_.is_open()) + { + std::lock_guard lock(mtx_); + file_.close(); + } + } + + bool ReadFile::IsOpen() const + { + std::lock_guard lock(mtx_); + return file_.is_open(); + } + + std::string ReadFile::ReadAllText() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return ""; + + std::ostringstream ss; + ss << file_.rdbuf(); + return ss.str(); + } + + std::vector ReadFile::ReadAllBinary() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + return ReadBytes(GetFileSize()); + } + + std::vector ReadFile::ReadLines() + { + // std::lock_guard lock(mtx_); + // if (!file_.is_open() && !Open()) + // return {}; + + // std::vector lines; + // std::string line; + // while (std::getline(file_, line)) + // { + // lines.push_back(line); + // } + // return lines; + + // std::lock_guard lock(mtx_); + if (!file_.is_open()) { + file_.open(filename_, std::ios::in | std::ios::binary); + if (!file_.is_open()) return {}; + } + file_.clear(); + file_.seekg(0, std::ios::beg); + + std::vector lines; + std::string line; + while (std::getline(file_, line)) lines.push_back(line); + return lines; + } + + std::vector ReadFile::ReadBytes(size_t count) + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + std::vector buffer(count); + file_.read(buffer.data(), count); + buffer.resize(file_.gcount()); + return buffer; + } + + size_t ReadFile::GetBytesBefore(const std::string &marker, bool includeMarker) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return 0; + + file_.clear(); // 清除EOF和错误状态 + file_.seekg(0, std::ios::beg); // 回到文件开头 + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file_.read(chunk, chunkSize) || file_.gcount() > 0) + { + buffer.append(chunk, file_.gcount()); + size_t pos = buffer.find(marker); + if (pos != std::string::npos) + { + // 如果 includeMarker 为 true,返回包含 marker 的长度 + if (includeMarker) + return pos + marker.size(); + else + return pos; + } + + // 保留末尾部分,避免 buffer 无限增长 + if (buffer.size() > marker.size()) + buffer.erase(0, buffer.size() - marker.size()); + + totalRead += file_.gcount(); + } + + return 0; + } + + std::vector ReadFile::ReadBytesFrom(size_t pos, size_t count) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return {}; + + size_t filesize = GetFileSize(); + if (pos >= filesize) + return {}; // 起始位置超出文件大小 + + file_.clear(); // 清除 EOF 和错误状态 + file_.seekg(pos, std::ios::beg); + if (!file_) + return {}; + + size_t bytes_to_read = count; + if (count == 0 || pos + count > filesize) + bytes_to_read = filesize - pos; // 读取到文件末尾 + + std::vector buffer(bytes_to_read); + file_.read(buffer.data(), bytes_to_read); + + // 实际读取的字节数可能少于请求的数量 + buffer.resize(file_.gcount()); + + return buffer; + } + + bool ReadFile::FileExists() const + { + return std::filesystem::exists(filename_); + } + + size_t ReadFile::GetFileSize() const + { + if (!FileExists()) + return 0; + return std::filesystem::file_size(filename_); + } + + void ReadFile::Reset() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + { + file_.clear(); + file_.seekg(0, std::ios::beg); + } + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals() + { + // 忽略全部的信号 + for (int ii = 1; ii <= 64; ii++) + signal(ii, SIG_IGN); + } + + std::string Ltrim(const std::string &s) + { + size_t start = 0; + while (start < s.size() && std::isspace(static_cast(s[start]))) + { + ++start; + } + return s.substr(start); + } + + std::string Rtrim(const std::string &s) + { + if (s.empty()) + return s; + + size_t end = s.size(); + while (end > 0 && std::isspace(static_cast(s[end - 1]))) + { + --end; + } + return s.substr(0, end); + } + + std::string LRtrim(const std::string &s) + { + return Ltrim(Rtrim(s)); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} \ No newline at end of file diff --git a/VideoProsessing/NetraLib/src/encrypt.cpp b/VideoProsessing/NetraLib/src/encrypt.cpp new file mode 100644 index 0000000..a8aea93 --- /dev/null +++ b/VideoProsessing/NetraLib/src/encrypt.cpp @@ -0,0 +1,91 @@ +#include "encrypt.hpp" +#include + +namespace encrypt +{ + string MD5(const string &info) + { + auto leftrotate = [](uint32_t x, uint32_t c) -> uint32_t { return (x << c) | (x >> (32 - c)); }; + + static const uint32_t s[64] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + static const uint32_t K[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + uint32_t a0 = 0x67452301; + uint32_t b0 = 0xefcdab89; + uint32_t c0 = 0x98badcfe; + uint32_t d0 = 0x10325476; + + std::vector msg(info.begin(), info.end()); + uint64_t bit_len = static_cast(msg.size()) * 8ULL; + msg.push_back(0x80); + while ((msg.size() % 64) != 56) msg.push_back(0x00); + for (int i = 0; i < 8; ++i) msg.push_back(static_cast((bit_len >> (8 * i)) & 0xff)); + + for (size_t offset = 0; offset < msg.size(); offset += 64) + { + uint32_t M[16]; + for (int i = 0; i < 16; ++i) + { + size_t j = offset + i * 4; + M[i] = static_cast(msg[j]) | + (static_cast(msg[j + 1]) << 8) | + (static_cast(msg[j + 2]) << 16) | + (static_cast(msg[j + 3]) << 24); + } + + uint32_t A = a0, B = b0, C = c0, D = d0; + for (uint32_t i = 0; i < 64; ++i) + { + uint32_t F, g; + if (i < 16) { F = (B & C) | ((~B) & D); g = i; } + else if (i < 32) { F = (D & B) | ((~D) & C); g = (5 * i + 1) % 16; } + else if (i < 48) { F = B ^ C ^ D; g = (3 * i + 5) % 16; } + else { F = C ^ (B | (~D)); g = (7 * i) % 16; } + + F = F + A + K[i] + M[g]; + A = D; + D = C; + C = B; + B = B + leftrotate(F, s[i]); + } + + a0 += A; b0 += B; c0 += C; d0 += D; + } + + uint8_t digest[16]; + auto u32_to_le = [](uint32_t v, uint8_t out[4]) { + out[0] = static_cast(v & 0xff); + out[1] = static_cast((v >> 8) & 0xff); + out[2] = static_cast((v >> 16) & 0xff); + out[3] = static_cast((v >> 24) & 0xff); + }; + u32_to_le(a0, digest + 0); + u32_to_le(b0, digest + 4); + u32_to_le(c0, digest + 8); + u32_to_le(d0, digest + 12); + + static const char *hex = "0123456789abcdef"; + std::string out; + out.resize(32); + for (int i = 0; i < 16; ++i) + { + out[i * 2] = hex[(digest[i] >> 4) & 0x0f]; + out[i * 2 + 1] = hex[digest[i] & 0x0f]; + } + return out; + } +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/MultiDeviceSyncConfig.json b/VideoProsessing/OrbbecSDK_v2.5.5/bin/MultiDeviceSyncConfig.json new file mode 100644 index 0000000..416d088 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/bin/MultiDeviceSyncConfig.json @@ -0,0 +1,30 @@ +{ + "version": "1.0.0", + "configTime": "2023/01/01", + "devices": [ + { + "sn": "CP2194200060", + "syncConfig": { + "syncMode": "OB_MULTI_DEVICE_SYNC_MODE_PRIMARY", + "depthDelayUs": 0, + "colorDelayUs": 0, + "trigger2ImageDelayUs": 0, + "triggerOutEnable": true, + "triggerOutDelayUs": 0, + "framesPerTrigger": 1 + } + }, + { + "sn": "CP0Y8420004K", + "syncConfig": { + "syncMode": "OB_MULTI_DEVICE_SYNC_MODE_SECONDARY", + "depthDelayUs": 0, + "colorDelayUs": 0, + "trigger2ImageDelayUs": 0, + "triggerOutEnable": true, + "triggerOutDelayUs": 0, + "framesPerTrigger": 1 + } + } + ] +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_callback b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_callback new file mode 100644 index 0000000..8e003fa Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_callback differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_color b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_color new file mode 100644 index 0000000..a1ab635 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_color differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_common_usages b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_common_usages new file mode 100644 index 0000000..5c002f8 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_common_usages differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_confidence b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_confidence new file mode 100644 index 0000000..c8766c4 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_confidence differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_coordinate_transform b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_coordinate_transform new file mode 100644 index 0000000..71939bc Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_coordinate_transform differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_depth b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_depth new file mode 100644 index 0000000..d053f5f Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_depth differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_depth_c b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_depth_c new file mode 100644 index 0000000..81f2967 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_depth_c differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_control b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_control new file mode 100644 index 0000000..7307b93 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_control differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_firmware_update b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_firmware_update new file mode 100644 index 0000000..8322ff3 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_firmware_update differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_forceip b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_forceip new file mode 100644 index 0000000..82e57a4 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_forceip differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_optional_depth_presets_update b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_optional_depth_presets_update new file mode 100644 index 0000000..c22b49e Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_optional_depth_presets_update differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_playback b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_playback new file mode 100644 index 0000000..532ba24 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_playback differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_record b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_record new file mode 100644 index 0000000..d07e54a Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_record differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_record_nogui b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_record_nogui new file mode 100644 index 0000000..45f0039 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_device_record_nogui differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_enumerate b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_enumerate new file mode 100644 index 0000000..7da6c81 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_enumerate differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_enumerate_c b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_enumerate_c new file mode 100644 index 0000000..db3ad54 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_enumerate_c differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_hdr b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_hdr new file mode 100644 index 0000000..5942bfc Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_hdr differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_hot_plugin b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_hot_plugin new file mode 100644 index 0000000..3faa9ab Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_hot_plugin differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_hw_d2c_align b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_hw_d2c_align new file mode 100644 index 0000000..8c98bdf Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_hw_d2c_align differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_imshow b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_imshow new file mode 100644 index 0000000..22bd311 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_imshow differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_imu b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_imu new file mode 100644 index 0000000..46d7ee6 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_imu differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_infrared b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_infrared new file mode 100644 index 0000000..2d76938 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_infrared differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_laser_interleave b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_laser_interleave new file mode 100644 index 0000000..50425f2 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_laser_interleave differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_logger b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_logger new file mode 100644 index 0000000..47b8cf5 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_logger differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_metadata b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_metadata new file mode 100644 index 0000000..1ebbd01 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_metadata differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_device b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_device new file mode 100644 index 0000000..10410bc Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_device differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_devices_firmware_update b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_devices_firmware_update new file mode 100644 index 0000000..f5cc0e8 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_devices_firmware_update differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_devices_sync b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_devices_sync new file mode 100644 index 0000000..444beae Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_devices_sync differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_devices_sync_gmsltrigger b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_devices_sync_gmsltrigger new file mode 100644 index 0000000..0a7fd26 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_devices_sync_gmsltrigger differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_streams b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_streams new file mode 100644 index 0000000..a5a7bc7 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_multi_streams differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_point_cloud b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_point_cloud new file mode 100644 index 0000000..250b3f7 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_point_cloud differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_post_processing b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_post_processing new file mode 100644 index 0000000..5fc65e7 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_post_processing differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_preset b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_preset new file mode 100644 index 0000000..b77dc7c Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_preset differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_quick_start b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_quick_start new file mode 100644 index 0000000..c9f22b1 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_quick_start differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_quick_start_c b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_quick_start_c new file mode 100644 index 0000000..5f337ef Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_quick_start_c differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_save_to_disk b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_save_to_disk new file mode 100644 index 0000000..d1e3c70 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_save_to_disk differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_sync_align b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_sync_align new file mode 100644 index 0000000..46b2ca0 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/bin/ob_sync_align differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/build_examples.sh b/VideoProsessing/OrbbecSDK_v2.5.5/build_examples.sh new file mode 100644 index 0000000..9c9c37f --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/build_examples.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +echo "Checking if apt-get is workable ..." +apt_workable=1 +# Check if apt-get is installed +if ! command -v apt-get &> /dev/null +then + echo "apt-get could not be found." + apt_workable=0 +fi + +# check if apt-get is working +if ! command -v sudo apt-get update &> /dev/null +then + echo "apt-get update failed. apt-get may not be working properly." + apt_workable=0 +fi + +if [ $apt_workable -eq 1 ] +then + #install compiler and tools + if ! g++ --version &> /dev/null || ! make --version &> /dev/null + then + echo "C++ Compiler and tools could not be found. It is required to build the examples." + echo "Do you want to install it via install build-essential? (y/n)" + read answer + if [ "$answer" == "y" ] + then + sudo apt-get install -y build-essential + fi + else + echo "C++ Compiler and tools is installed." + fi + + # install cmake + if ! cmake --version &> /dev/null + then + echo "Cmake could not be found. It is required to build the examples." + echo "Do you want to install cmake? (y/n)" + read answer + if [ "$answer" == "y" ] + then + sudo apt-get install -y cmake + fi + else + echo "cmake is installed." + fi + + # install libopencv-dev + if ! dpkg -l | grep libopencv-dev &> /dev/null || ! dpkg -l | grep libopencv &> /dev/null + then + echo "libopencv-dev or libopencv could not be found. Without opencv, part of the examples may not be built successfully." + echo "Do you want to install libopencv-dev and libopencv? (y/n)" + read answer + if [ "$answer" == "y" ] + then + sudo apt-get install -y libopencv + sudo apt-get install -y libopencv-dev + fi + else + echo "libopencv-dev is installed." + fi +else + echo "apt-get is not workable, network connection may be down or the system may not have internet access. Build examples may not be successful." +fi + +# restore current directory +current_dir=$(pwd) + +# cd to the directory where this script is located +cd "$(dirname "$0")" +project_dir=$(pwd) +examples_dir=$project_dir/examples + +#detect cpu core count +cpu_count=$(grep -c ^processor /proc/cpuinfo) +half_cpu_count=$((cpu_count/2)) +if [ $half_cpu_count -eq 0 ] +then + half_cpu_count=1 +fi + +#cmake +echo "Building examples..." +mkdir build +cd build +cmake -DCMAKE_BUILD_TYPE=Release -DOB_BUILD_LINUX=ON -DCMAKE_INSTALL_PREFIX="$project_dir" "$examples_dir" +echo "Building examples with $half_cpu_count threads..." +cmake --build . -- -j$half_cpu_count # build with thread count equal to half of cpu count +# install the executable files to the project directory +make install + +# clean up +cd $project_dir +rm -rf build + +echo "OrbbecSDK examples built successfully!" +echo "The executable files located in: $project_dir/bin" + +cd $current_dir diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/CMakeLists.txt new file mode 100644 index 0000000..9f67489 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) + +project(orbbec_sdk_exampes) + +set(CMAKE_CXX_STANDARD 11) + +option(OB_BUILD_PCL_EXAMPLES "Build Point Cloud Library examples" OFF) +option(OB_BUILD_OPEN3D_EXAMPLES "Build Open3D examples" OFF) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +if(MSVC OR CMAKE_GENERATOR STREQUAL "Xcode") + message(STATUS "Using multi-config generator: ${CMAKE_GENERATOR}") + foreach(OUTPUTCONFIG DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG_UPPER) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}") + endforeach() +endif() + +set(OrbbecSDK_DIR ${CMAKE_CURRENT_LIST_DIR}/../lib) +find_package(OrbbecSDK REQUIRED) + +if(APPLE) + set(CMAKE_MACOSX_RPATH ON) + set(CMAKE_INSTALL_RPATH "@loader_path/../lib") + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +elseif(UNIX) + set(CMAKE_SKIP_BUILD_RPATH FALSE) + set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib") + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +endif() + +add_subdirectory(src) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.enumerate/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.enumerate/CMakeLists.txt new file mode 100644 index 0000000..4576bc8 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.enumerate/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_enumerate) + +add_executable(${PROJECT_NAME} enumerate.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.enumerate/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.enumerate/README.md new file mode 100644 index 0000000..c767df7 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.enumerate/README.md @@ -0,0 +1,127 @@ +# C++ Sample: 0.basic.enumerate + +## Overview + +Use the SDK interface to obtain camera-related information, including model, various sensors, and sensor-related configurations. + +### Knowledge + +Context is the environment context, the first object created during initialization, which can be used to perform some settings, including but not limited to device status change callbacks, log level settings, etc. Context can access multiple Devices. + +Device is the device object, which can be used to obtain the device information, such as the model, serial number, and various sensors.One actual hardware device corresponds to one Device object. + +## Code overview + +1. Create a context + + ```cpp + // Create a context. + ob::Context context; + ``` + +2. Check if there is a camera connected + + ```cpp + // Query the list of connected devices. + auto deviceList = context.queryDeviceList(); + if(deviceList->getCount() < 1) { + std::cout << "No device found! Please connect a supported device and retry this program." << std::endl; + return -1; + } + ``` + +3. Obtain and output relevant information of the access device + + ```cpp + std::cout << "enumerated devices: " << std::endl; + + std::shared_ptr device = nullptr; + std::shared_ptr deviceInfo = nullptr; + for(uint32_t index = 0; index < deviceList->getCount(); index++) { + // Get device from deviceList. + device = deviceList->getDevice(index); + // Get device information from device + deviceInfo = device->getDeviceInfo(); + std::cout << " - " << index << ". name: " << deviceInfo->getName() << " pid: " << deviceInfo->getPid() << " SN: " << deviceInfo->getSerialNumber() + << std::endl; + } + ``` + +4. Wait for keyboard input to select device + + ```cpp + // select a device. + int deviceSelected = ob_smpl::getInputOption(); + if(deviceSelected == -1) { + break; + } + ``` + +5. Output device sensors and wait for keyboard input + + ```cpp + // Enumerate sensors. + void enumerateSensors(std::shared_ptr device) { + while(true) { + std::cout << "Sensor list: " << std::endl; + // Get the list of sensors. + auto sensorList = device->getSensorList(); + for(uint32_t index = 0; index < sensorList->getCount(); index++) { + // Get the sensor type. + auto sensorType = sensorList->getSensorType(index); + std::cout << " - " << index << "." + << "sensor type: " << ob::TypeHelper::convertOBSensorTypeToString(sensorType) << std::endl; + } + + std::cout << "Select a sensor to enumerate its streams(input sensor index or \'ESC\' to enumerate device): " << std::endl; + + // Select a sensor. + int sensorSelected = ob_smpl::getInputOption(); + if(sensorSelected == -1) { + break; + } + + // Get sensor from sensorList. + auto sensor = sensorList->getSensor(sensorSelected); + enumerateStreamProfiles(sensor); + } + } + ``` + +6. Output information about the selected sensor + + ```cpp + // Enumerate stream profiles. + void enumerateStreamProfiles(std::shared_ptr sensor) { + // Get the list of stream profiles. + auto streamProfileList = sensor->getStreamProfileList(); + // Get the sensor type. + auto sensorType = sensor->getType(); + for(uint32_t index = 0; index < streamProfileList->getCount(); index++) { + // Get the stream profile. + auto profile = streamProfileList->getProfile(index); + if(sensorType == OB_SENSOR_IR || sensorType == OB_SENSOR_COLOR || sensorType == OB_SENSOR_DEPTH || sensorType == OB_SENSOR_IR_LEFT + || sensorType == OB_SENSOR_IR_RIGHT) { + printStreamProfile(profile, index); + } + else if(sensorType == OB_SENSOR_ACCEL) { + printAccelProfile(profile, index); + } + else if(sensorType == OB_SENSOR_GYRO) { + printGyroProfile(profile, index); + } + else { + break; + } + } + } + ``` + +## Run Sample + +In the window, enter the relevant information of the device sensor you want to view according to the prompts. +Press the Esc key in the window to exit the program. + +### Result + +![image](../../docs/resource/enumerate.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.enumerate/enumerate.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.enumerate/enumerate.cpp new file mode 100644 index 0000000..2b155db --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.enumerate/enumerate.cpp @@ -0,0 +1,170 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include +#include + +// get input option +int getInputOption() { + char inputOption = ob_smpl::waitForKeyPressed(); + if(inputOption == ESC_KEY) { + return -1; + } + return inputOption - '0'; +} + +// Print stream profile information. +void printStreamProfile(std::shared_ptr profile, uint32_t index) { + // Get the video profile. + auto videoProfile = profile->as(); + // Get the format. + auto formatName = profile->getFormat(); + // Get the width. + auto width = videoProfile->getWidth(); + // Get the height. + auto height = videoProfile->getHeight(); + // Get the fps. + auto fps = videoProfile->getFps(); + std::cout << index << "." + << "format: " << ob::TypeHelper::convertOBFormatTypeToString(formatName) << ", " + << "res: " << width << "*" << height << ", " + << "fps: " << fps << std::endl; +} + +// Print accel profile information. +void printAccelProfile(std::shared_ptr profile, uint32_t index) { + // Get the profile of accel. + auto accProfile = profile->as(); + // Get the rate of accel. + auto accRate = accProfile->getSampleRate(); + std::cout << index << "." + << "acc rate: " << ob::TypeHelper::convertOBIMUSampleRateTypeToString(accRate) << std::endl; +} + +// Print gyro profile information. +void printGyroProfile(std::shared_ptr profile, uint32_t index) { + // Get the profile of gyro. + auto gyroProfile = profile->as(); + // Get the rate of gyro. + auto gyroRate = gyroProfile->getSampleRate(); + std::cout << index << "." + << "gyro rate: " << ob::TypeHelper::convertOBIMUSampleRateTypeToString(gyroRate) << std::endl; +} + +// Enumerate stream profiles. +void enumerateStreamProfiles(std::shared_ptr sensor) { + // Get the list of stream profiles. + auto streamProfileList = sensor->getStreamProfileList(); + // Get the sensor type. + auto sensorType = sensor->getType(); + for(uint32_t index = 0; index < streamProfileList->getCount(); index++) { + // Get the stream profile. + auto profile = streamProfileList->getProfile(index); + if(sensorType == OB_SENSOR_IR || sensorType == OB_SENSOR_COLOR || sensorType == OB_SENSOR_DEPTH || sensorType == OB_SENSOR_IR_LEFT + || sensorType == OB_SENSOR_IR_RIGHT || sensorType == OB_SENSOR_CONFIDENCE) { + printStreamProfile(profile, index); + } + else if(sensorType == OB_SENSOR_ACCEL) { + printAccelProfile(profile, index); + } + else if(sensorType == OB_SENSOR_GYRO) { + printGyroProfile(profile, index); + } + else { + break; + } + } +} + +// Enumerate sensors. +void enumerateSensors(std::shared_ptr device) { + while(true) { + std::cout << "Sensor list: " << std::endl; + // Get the list of sensors. + auto sensorList = device->getSensorList(); + for(uint32_t index = 0; index < sensorList->getCount(); index++) { + // Get the sensor type. + auto sensorType = sensorList->getSensorType(index); + std::cout << " - " << index << "." + << "sensor type: " << ob::TypeHelper::convertOBSensorTypeToString(sensorType) << std::endl; + } + + std::cout << "Select a sensor to enumerate its streams(input sensor index or \'ESC\' to enumerate device): " << std::endl; + + // Select a sensor. + int sensorSelected = ob_smpl::getInputOption(); + if(sensorSelected >= static_cast(sensorList->getCount()) || sensorSelected < 0) { + if(sensorSelected == -1) { + break; + } + else { + std::cout << "\nInvalid input, please reselect the sensor!\n"; + continue; + } + } + + // Get sensor from sensorList. + auto sensor = sensorList->getSensor(sensorSelected); + enumerateStreamProfiles(sensor); + } +} + +int main(void) try { + + // Create a context. + ob::Context context; + + while(true) { + // Query the list of connected devices. + auto deviceList = context.queryDeviceList(); + if(deviceList->getCount() < 1) { + std::cout << "No device found! Please connect a supported device and retry this program." << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + return -1; + } + + std::cout << "enumerated devices: " << std::endl; + + std::shared_ptr device = nullptr; + std::shared_ptr deviceInfo = nullptr; + for(uint32_t index = 0; index < deviceList->getCount(); index++) { + // Get device from deviceList. + device = deviceList->getDevice(index); + // Get device information from device + deviceInfo = device->getDeviceInfo(); + std::cout << " " << index << "- device name: " << deviceInfo->getName() << ", device pid: 0x" << std::hex << std::setw(4) << std::setfill('0') + << deviceInfo->getPid() << std::dec << " ,device SN: " << deviceInfo->getSerialNumber() << ", connection type:" << deviceInfo->getConnectionType() << std::endl; + } + + std::cout << "Select a device to enumerate its sensors (Input device index or \'ESC\' to exit program):" << std::endl; + + // select a device. + int deviceSelected = ob_smpl::getInputOption(); + if(deviceSelected >= static_cast(deviceList->getCount()) || deviceSelected < 0) { + if(deviceSelected == -1) { + break; + } + else { + std::cout << "\nInvalid input, please reselect the device!\n"; + continue; + } + } + + // Get the device. + auto selectedDevice = deviceList->getDevice(deviceSelected); + enumerateSensors(selectedDevice); + } + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.quick_start/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.quick_start/CMakeLists.txt new file mode 100644 index 0000000..3660817 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.quick_start/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_quick_start) + +add_executable(${PROJECT_NAME} quick_start.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.quick_start/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.quick_start/README.md new file mode 100644 index 0000000..ad78206 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.quick_start/README.md @@ -0,0 +1,60 @@ +# C++ Sample: 0.basic.quick_start + +## Overview + +Use the SDK interface to quickly obtain the camera video stream and display it in the window. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions + +Frameset is a combination of different types of Frames + +win is used to display the frame data. + +## Code overview + +1. Instantiate the pipeline using the default configuration file and quickly open the video stream + + ```cpp + // Create a pipeline. + ob::Pipeline pipe; + + // Start the pipeline with default config. + // Modify the default configuration by the configuration file: "OrbbecSDKConfig.xml" + pipe.start(); + ``` + +2. Create a window for showing the frames, and set the size of the window + + ```cpp + // Create a window for showing the frames, and set the size of the window. + ob_smpl::CVWindow win("QuickStart", 1280, 720, ob_smpl::ARRANGE_ONE_ROW); + ``` + +3. Open the window and display the video stream. The video stream waits for a frame of data in a blocking manner. The frame is a composite frame containing the frame data of all streams enabled in the configuration, and the waiting timeout of the frame is set + + ```cpp + while(win.run()) { + // Wait for frameSet from the pipeline, the default timeout is 1000ms. + auto frameSet = pipe.waitForFrameset(); + + // Push the frames to the window for showing. + win.pushFramesToView(frameSet); + } + ``` + +4. Use pipeline to close the video stream + + ```cpp + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + ``` + +## Run Sample + +Press the Esc key in the window to exit the program. + +### Result + +![image](../../docs/resource/quick_start.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.quick_start/quick_start.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.quick_start/quick_start.cpp new file mode 100644 index 0000000..2ff452b --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/0.basic.quick_start/quick_start.cpp @@ -0,0 +1,39 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +int main(void) try { + // Create a pipeline. + ob::Pipeline pipe; + + // Start the pipeline with default config. + // Modify the default configuration by the configuration file: "OrbbecSDKConfig.xml" + pipe.start(); + + // Create a window for showing the frames, and set the size of the window. + ob_smpl::CVWindow win("QuickStart", 1280, 720, ob_smpl::ARRANGE_ONE_ROW); + + while(win.run()) { + // Wait for frameSet from the pipeline, the default timeout is 1000ms. + auto frameSet = pipe.waitForFrameset(); + + // Push the frames to the window for showing. + win.pushFramesToView(frameSet); + } + + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.callback/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.callback/CMakeLists.txt new file mode 100644 index 0000000..98d409d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.callback/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_callback) + +add_executable(${PROJECT_NAME} callback.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.callback/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.callback/README.md new file mode 100644 index 0000000..d05c159 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.callback/README.md @@ -0,0 +1,96 @@ +# C++ Sample: 1.stream.callback + +## Overview + +In this sample,user can get the depth、RGB、IR image.This sample also support users can perform user-defined operations such as data acquisition, data processing, and data modification within the callback function. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions. + +Device is a class that can be used to get device information, parameters, and a list of contained sensors. + +Sensor can be used to obtain different components of the camera and the stream of the component, for example, RGB, IR, Depth stream can be obtained through the RGB, IR, Depth sensor. + +## code overview + +1. Create the pipeline instance using the default configuration and create a config instance to enable or disable the streams.Get the device instance from pipeline,and then get the sensor instance from device. + + ```c++ + // Create a pipeline. + ob::Pipeline pipe; + + // Configure which streams to enable or disable for the Pipeline by creating a Config. + std::shared_ptr config = std::make_shared(); + + // Get device from pipeline. + auto device = pipe.getDevice(); + + // Get sensorList from device. + auto sensorList = device->getSensorList(); + ``` + +2. Get only the sensor for the VideoStream,enable the stream from these sensor. + + ```c++ + for(uint32_t index = 0; index < sensorList->getCount(); index++) { + // Query all supported infrared sensor type and enable the infrared stream. + // For dual infrared device, enable the left and right infrared streams. + // For single infrared device, enable the infrared stream. + OBSensorType sensorType = sensorList->getSensorType(index); + + // exclude non-video sensor type + if(!ob::TypeHelper::isVideoSensorType(sensorType)) { + continue; + } + + // Enable the stream for the sensor type. + config->enableStream(sensorType); + } + ``` + +3. In this callback function, you can add what you want to do with the data.Avoid performing complex computational operations within callback functions; prolonged operations can lead to data frame drops. It is recommended to use a queue for processing. + + ```c++ + // Start the pipeline with callback. + pipe.start(config, [&](std::shared_ptr output) { + std::lock_guard lock(framesetMutex); + frameset = output; + }); + ``` + +4. Render window + + ```c++ + while(win.run()) { + std::lock_guard lock(framesetMutex); + + if(frameset == nullptr) { + continue; + } + + // Rendering display + win.pushFramesToView(frameset); + } + ``` + +5. stop pipeline + + ```c++ + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + ``` + +## Run Sample + +If you are on Windows, you can switch to the directory `OrbbecSDK-dev/build/win_XX/bin` to find the `ob_callback.exe`. + +If you are on linux, you can switch to the directory `OrbbecSDK-dev/build/linux_XX/bin` to find the `ob_callback`. + +### Key introduction + +Press the Esc key in the window to exit the program. + +### Result + +![result](../../docs/resource/callback.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.callback/callback.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.callback/callback.cpp new file mode 100644 index 0000000..3111b25 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.callback/callback.cpp @@ -0,0 +1,80 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#define IS_ASTRA_MINI_DEVICE(pid) (pid == 0x069d || pid == 0x065b || pid == 0x065e) + +int main(void) try { + + // Create a pipeline. + ob::Pipeline pipe; + + // Configure which streams to enable or disable for the Pipeline by creating a Config. + std::shared_ptr config = std::make_shared(); + + // Get device from pipeline. + auto device = pipe.getDevice(); + + // Get sensorList from device. + auto sensorList = device->getSensorList(); + + for(uint32_t index = 0; index < sensorList->getCount(); index++) { + // Query all supported infrared sensor type and enable the infrared stream. + // For dual infrared device, enable the left and right infrared streams. + // For single infrared device, enable the infrared stream. + OBSensorType sensorType = sensorList->getSensorType(index); + + // exclude non-video sensor type + if(!ob::TypeHelper::isVideoSensorType(sensorType)) { + continue; + } + + if(IS_ASTRA_MINI_DEVICE(device->getDeviceInfo()->getPid())) { + if(sensorType == OB_SENSOR_COLOR) { + continue; + } + } + + // Enable the stream for the sensor type. + config->enableStream(sensorType); + } + + std::mutex framesetMutex; + std::shared_ptr frameset = nullptr; + + // Start the pipeline with callback. + pipe.start(config, [&](std::shared_ptr output) { + std::lock_guard lock(framesetMutex); + frameset = output; + }); + + // Create a window for rendering, and set the size of the window. + ob_smpl::CVWindow win("Callback", 1280, 720, ob_smpl::ARRANGE_GRID); + + while(win.run()) { + std::lock_guard lock(framesetMutex); + + if(frameset == nullptr) { + continue; + } + + // Rendering display + win.pushFramesToView(frameset); + } + + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.color/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.color/CMakeLists.txt new file mode 100644 index 0000000..f770859 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.color/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_color) + +add_executable(${PROJECT_NAME} color.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.color/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.color/README.md new file mode 100644 index 0000000..573a64d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.color/README.md @@ -0,0 +1,47 @@ +# C++ Sample: 1.stream.color + +## Overview + +Use the SDK interface to obtain the camera's color stream and display it in the window. + +### Knowledge + +config is the configuration of the camera +Frameset is a combination of different types of Frames + +## Code overview + +1. Configure the output color stream and open the video stream.You must configure this before calling pipe.start(). + + ```cpp + // Configure which streams to enable or disable for the Pipeline by creating a Config. + std::shared_ptr config = std::make_shared(); + + // Enable color video stream. + config->enableVideoStream(OB_STREAM_COLOR); + ``` + +2. After waiting for a while, get the color stream in the frameset and display it in the window + + ```cpp + while(win.run()) { + // Wait for up to 100ms for a frameset in blocking mode. + auto frameSet = pipe.waitForFrameset(); + if(frameSet == nullptr) { + continue; + } + + // get color frame from frameset. + auto colorFrame = frameSet->getFrame(OB_FRAME_COLOR); + // Render colorFrame. + win.pushFramesToView(colorFrame); + } + ``` + +## Run Sample + +Press the Esc key in the window to exit the program. + +### Result + +![result](../../docs/resource/color.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.color/color.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.color/color.cpp new file mode 100644 index 0000000..e0e6f85 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.color/color.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +int main(void) try { + + // Create a pipeline with default device. + ob::Pipeline pipe; + + // Configure which streams to enable or disable for the Pipeline by creating a Config. + std::shared_ptr config = std::make_shared(); + + // Enable color video stream. + config->enableVideoStream(OB_STREAM_COLOR); + + // Start the pipeline with config. + pipe.start(config); + + // Create a window for rendering and set the resolution of the window. + ob_smpl::CVWindow win("Color"); + + while(win.run()) { + // Wait for up to 100ms for a frameset in blocking mode. + auto frameSet = pipe.waitForFrameset(); + if(frameSet == nullptr) { + continue; + } + + // get color frame from frameset. + auto colorFrame = frameSet->getFrame(OB_FRAME_COLOR); + // Render colorFrame. + win.pushFramesToView(colorFrame); + } + + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.confidence/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.confidence/CMakeLists.txt new file mode 100644 index 0000000..b0fe16f --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.confidence/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_confidence) + +add_executable(${PROJECT_NAME} confidence.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.confidence/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.confidence/README.md new file mode 100644 index 0000000..0c59820 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.confidence/README.md @@ -0,0 +1,58 @@ +# C++ Sample: 1.stream.confidence + +## Overview + +Use the SDK interface to obtain the depth and confidence stream of the camera and display them in the window + +### Knowledge + +Enabling the confidence stream requires the depth stream to be active, and its resolution and frame rate must match the depth stream's. + +## Code overview + +1. Configure the depth and confidence streams, then start the pipeline with this configuration. All stream configurations must be completed before calling pipe.start(). + + ```cpp + // By creating config to configure which streams to enable or disable for the pipeline, here the depth stream will be enabled. + std::shared_ptr config = std::make_shared(); + + // Enable depth stream. + config->enableVideoStream(OB_STREAM_DEPTH); + + // Enable confidence stream. The resolution and fps of confidence must match depth stream. + auto enabledProfiles = config->getEnabledStreamProfileList(); + if(enabledProfiles) { + for(uint32_t i = 0; i < enabledProfiles->getCount(); i++) { + auto profile = enabledProfiles->getProfile(i); + if(profile && profile->getType() == OB_STREAM_DEPTH) { + auto depthProfile = profile->as(); + if(depthProfile) { + config->enableVideoStream(OB_STREAM_CONFIDENCE, depthProfile->getWidth(), depthProfile->getHeight(), depthProfile->getFps()); + } + break; + } + } + } + ``` + +2. After waiting for a while, get the depth and confidence stream in the frameset and display them in the window + + ```cpp + while(win.run()) { + // Wait for up to 100ms for a frameset in blocking mode. + auto frameSet = pipe.waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + // Render frame in the wisndow. + win.pushFramesToView(frameSet); + } + ``` + +## Run Sample + +Press the Esc key in the window to exit the program. + +### Result + +![image](../../docs/resource/confidence.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.confidence/confidence.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.confidence/confidence.cpp new file mode 100644 index 0000000..d892807 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.confidence/confidence.cpp @@ -0,0 +1,73 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include + +#define IS_GEMINI_435LE(pid) (pid == 0x0815) + +int main(void) try { + + // Create a pipeline with default device. + ob::Pipeline pipe; + + // This example only supports Gemini 435Le device + auto device = pipe.getDevice(); + if(!IS_GEMINI_435LE(device->getDeviceInfo()->getPid())) { + std::cout << "This example only supports Gemini 435Le device." << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + return 0; + } + + // By creating config to configure which streams to enable or disable for the pipeline, here the depth stream will be enabled. + std::shared_ptr config = std::make_shared(); + + // Enable depth stream. + config->enableVideoStream(OB_STREAM_DEPTH); + + // Enable confidence stream. The resolution and fps of confidence must match depth stream. + auto enabledProfiles = config->getEnabledStreamProfileList(); + if(enabledProfiles) { + for(uint32_t i = 0; i < enabledProfiles->getCount(); i++) { + auto profile = enabledProfiles->getProfile(i); + if(profile && profile->getType() == OB_STREAM_DEPTH) { + auto depthProfile = profile->as(); + if(depthProfile) { + config->enableVideoStream(OB_STREAM_CONFIDENCE, depthProfile->getWidth(), depthProfile->getHeight(), depthProfile->getFps()); + } + break; + } + } + } + // Start the pipeline with config. + pipe.start(config); + + // Create a window for rendering, and set the resolution of the window. + ob_smpl::CVWindow win("Confidence", 1280, 720, ob_smpl::ARRANGE_GRID); + + while(win.run()) { + // Wait for up to 100ms for a frameset in blocking mode. + auto frameSet = pipe.waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + // Render frame in the wisndow. + win.pushFramesToView(frameSet); + } + + // Stop the pipeline + pipe.stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.depth/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.depth/CMakeLists.txt new file mode 100644 index 0000000..b69ffa8 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.depth/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_depth) + +add_executable(${PROJECT_NAME} depth.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + + + + + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.depth/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.depth/README.md new file mode 100644 index 0000000..14e08e0 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.depth/README.md @@ -0,0 +1,51 @@ +# C++ Sample: 1.stream.depth + +## Overview + +Use the SDK interface to obtain the depth stream of the camera and display it in the window + +### Knowledge + +DepthFrame can obtain relevant information about the depth + +## code overview + +1. Configure the output color stream and open the video stream.You must configure this before calling pipe.start(). + + ```cpp + // By creating config to configure which streams to enable or disable for the pipeline, here the depth stream will be enabled. + std::shared_ptr config = std::make_shared(); + + //This is the default depth streamprofile that is enabled. If you want to modify it, you can do so in the configuration file. + config->enableVideoStream(OB_STREAM_DEPTH); + + ``` + +2. Calculate the distance from the center pixel to the opposite side from the acquired Y16 depth stream format and display it in the window. The distance is refreshed every 30 frames.The default depth unit for the SDK is millimeters. + + ```cpp + // Get the depth Frame form depthFrameRaw. + auto depthFrame = depthFrameRaw->as(); + // for Y16 format depth frame, print the distance of the center pixel every 30 frames. + if(depthFrame->getIndex() % 30 == 0 && depthFrame->getFormat() == OB_FORMAT_Y16) { + uint32_t width = depthFrame->getWidth(); + uint32_t height = depthFrame->getHeight(); + float scale = depthFrame->getValueScale(); + const uint16_t *data = reinterpret_cast(depthFrame->getData()); + + // pixel value multiplied by scale is the actual distance value in millimeters. + float centerDistance = data[width * height / 2 + width / 2] * scale; + + // // attention: if the distance is 0, it means that the depth camera cannot detect the object (may be out of detection range). + win.addLog("Facing an object at a distance of " + ob_smpl::toString(centerDistance, 3) + " mm. "); + } + ``` + +## Run Sample + +Moving the camera can obtain the change in the distance across the center pixel +Press the Esc key in the window to exit the program. + +### Result + +![image](../../docs/resource/depth.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.depth/depth.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.depth/depth.cpp new file mode 100644 index 0000000..1911325 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.depth/depth.cpp @@ -0,0 +1,71 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include + +int main(void) try { + + // Create a pipeline with default device. + ob::Pipeline pipe; + + // By creating config to configure which streams to enable or disable for the pipeline, here the depth stream will be enabled. + std::shared_ptr config = std::make_shared(); + + // This is the default depth streamprofile that is enabled. If you want to modify it, you can do so in the configuration file. + config->enableVideoStream(OB_STREAM_DEPTH); + + // Start the pipeline with config. + pipe.start(config); + + // Create a window for rendering, and set the resolution of the window. + ob_smpl::CVWindow win("Depth"); + + while(win.run()) { + // Wait for up to 100ms for a frameset in blocking mode. + auto frameSet = pipe.waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + + // Get the depth frame raw from frameset. + auto depthFrameRaw = frameSet->getFrame(OB_FRAME_DEPTH); + if(!depthFrameRaw) { + continue; + } + + // Get the depth Frame form depthFrameRaw. + auto depthFrame = depthFrameRaw->as(); + // for Y16 format depth frame, print the distance of the center pixel every 30 frames. + if(depthFrame->getIndex() % 30 == 0 && depthFrame->getFormat() == OB_FORMAT_Y16) { + uint32_t width = depthFrame->getWidth(); + uint32_t height = depthFrame->getHeight(); + float scale = depthFrame->getValueScale(); + const uint16_t *data = reinterpret_cast(depthFrame->getData()); + + // pixel value multiplied by scale is the actual distance value in millimeters. + float centerDistance = data[width * height / 2 + width / 2] * scale; + + // // attention: if the distance is 0, it means that the depth camera cannot detect the object (may be out of detection range). + win.addLog("Facing an object at a distance of " + ob_smpl::toString(centerDistance, 3) + " mm. "); + } + + // Render frame in the window. + win.pushFramesToView(depthFrame); + } + + // Stop the pipeline + pipe.stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.imu/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.imu/CMakeLists.txt new file mode 100644 index 0000000..01a1e69 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.imu/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_imu) + +add_executable(${PROJECT_NAME} imu.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.imu/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.imu/README.md new file mode 100644 index 0000000..0d27e23 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.imu/README.md @@ -0,0 +1,66 @@ +# C++ Sample: 1.stream.imu + +## Overview + +Use the SDK interface to obtain the camera's internal imu data and output it + +### Knowledge + +AccelFrame measures the acceleration of x, y, and z in m/s^2 +GyroFrame measures the angular velocity of x, y, and z in rad/s + +Frameset is a combination of different types of Frames.imu data stream can be obtained through frameset + +## code overview + +1. Configure output imu related information and open stream.You must configure this before calling pipe.start(). + + ```cpp + + // Configure which streams to enable or disable for the Pipeline by creating a Config. + std::shared_ptr config = std::make_shared(); + + // Enable Accel stream. + config->enableAccelStream(); + + // Enable Gyro stream. + config->enableGyroStream(); + + // Only FrameSet that contains all types of data frames will be output. + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + + ``` + +2. Instantiate pipeline, configure output imu related information and open stream + + ```cpp + auto accelFrameRaw = frameSet->getFrame(OB_FRAME_ACCEL); + auto accelFrame = accelFrameRaw->as(); + auto accelIndex = accelFrame->getIndex(); + auto accelTimeStampUs = accelFrame->getTimeStampUs(); + auto accelTemperature = accelFrame->getTemperature(); + auto accelType = accelFrame->getType(); + if(accelIndex % 50 == 0) { // print information every 50 frames. + auto accelValue = accelFrame->getValue(); + printImuValue(accelValue, accelIndex, accelTimeStampUs, accelTemperature, accelType, "m/s^2"); + } + + auto gyroFrameRaw = frameSet->getFrame(OB_FRAME_GYRO); + auto gyroFrame = gyroFrameRaw->as(); + auto gyroIndex = gyroFrame->getIndex(); + auto gyroTimeStampUs = gyroFrame->getTimeStampUs(); + auto gyroTemperature = gyroFrame->getTemperature(); + auto gyroType = gyroFrame->getType(); + if(gyroIndex % 50 == 0) { // print information every 50 frames. + auto gyroValue = gyroFrame->getValue(); + printImuValue(gyroValue, gyroIndex, gyroTimeStampUs, gyroTemperature, gyroType, "rad/s"); + } + ``` + +## Run Sample + +Press the Esc key in the window to exit the program. + +### Result + +![image](../../docs/resource/imu.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.imu/imu.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.imu/imu.cpp new file mode 100644 index 0000000..3c3389a --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.imu/imu.cpp @@ -0,0 +1,90 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_types.h" + +#include +#include + +void printImuValue(OBFloat3D obFloat3d, uint64_t index, uint64_t timeStampUs, float temperature, OBFrameType type, const std::string &unitStr) { + std::cout << "frame index: " < config = std::make_shared(); + + // Enable Accel stream. + config->enableAccelStream(); + + // Enable Gyro stream. + config->enableGyroStream(); + + // Only FrameSet that contains all types of data frames will be output. + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + + // Start the pipeline with config. + pipe.start(config); + + uint64_t accelCount = 0; + uint64_t gyroCount = 0; + while(true) { + auto key = ob_smpl::waitForKeyPressed(1); + if(key == ESC_KEY) { // Esc key to exit. + break; + } + auto frameSet = pipe.waitForFrameset(); + if(frameSet == nullptr) { + continue; + } + + auto accelFrameRaw = frameSet->getFrame(OB_FRAME_ACCEL); + auto accelFrame = accelFrameRaw->as(); + auto accelIndex = accelFrame->getIndex(); + auto accelTimeStampUs = accelFrame->getTimeStampUs(); + auto accelTemperature = accelFrame->getTemperature(); + auto accelType = accelFrame->getType(); + if(accelCount % 50 == 0) { // print information every 50 frames. + auto accelValue = accelFrame->getValue(); + printImuValue(accelValue, accelIndex, accelTimeStampUs, accelTemperature, accelType, "m/s^2"); + } + ++accelCount; + + auto gyroFrameRaw = frameSet->getFrame(OB_FRAME_GYRO); + auto gyroFrame = gyroFrameRaw->as(); + auto gyroIndex = gyroFrame->getIndex(); + auto gyroTimeStampUs = gyroFrame->getTimeStampUs(); + auto gyroTemperature = gyroFrame->getTemperature(); + auto gyroType = gyroFrame->getType(); + if(gyroCount % 50 == 0) { // print information every 50 frames. + auto gyroValue = gyroFrame->getValue(); + printImuValue(gyroValue, gyroIndex, gyroTimeStampUs, gyroTemperature, gyroType, "rad/s"); + } + ++gyroCount; + } + + pipe.stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.infrared/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.infrared/CMakeLists.txt new file mode 100644 index 0000000..35083bc --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.infrared/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_infrared) + +add_executable(${PROJECT_NAME} infrared.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.infrared/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.infrared/README.md new file mode 100644 index 0000000..6ab51d5 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.infrared/README.md @@ -0,0 +1,59 @@ +# C++ Sample: 1.stream.infrared + +## Overview + +Use the SDK interface to obtain the camera IR stream and display it in the window + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions. + +## code overview + +1. Configure IR sensor related information and enable the IR stream.You must configure this before calling pipe.start(). + + ```cpp + // Get the device from pipeline. + std::shared_ptr device = pipe.getDevice(); + + // Get the sensor list from device. + std::shared_ptr sensorList = device->getSensorList(); + + // Create a config for pipeline. + std::shared_ptr config = std::make_shared(); + + for(uint32_t index = 0; index < sensorList->getCount(); index++) { + // Query all supported infrared sensor type and enable the infrared stream. + // For dual infrared device, enable the left and right infrared streams. + // For single infrared device, enable the infrared stream. + OBSensorType sensorType = sensorList->getSensorType(index); + if(sensorType == OB_SENSOR_IR || sensorType == OB_SENSOR_IR_LEFT || sensorType == OB_SENSOR_IR_RIGHT) { + // Enable the stream with specified profile; + config->enableVideoStream(sensorType, OB_WIDTH_ANY, OB_HEIGHT_ANY, 30, OB_FORMAT_ANY); + } + } + ``` + +2. Open the window and output the IR stream + + ```cpp + ob_smpl::CVWindow win("Infrared", 1280, 720, ob_smpl::ARRANGE_ONE_ROW); + while(win.run()) { + // Wait for up to 100ms for a frameset in blocking mode. + auto frameSet = pipe.waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + + // Render a set of frame in the window. + win.pushFramesToView(frameSet); + } + ``` + +## Run Sample + +Press the Esc key in the window to exit the program. + +### Result + +![image](../../docs/resource/infrared.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.infrared/infrared.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.infrared/infrared.cpp new file mode 100644 index 0000000..2a8b527 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.infrared/infrared.cpp @@ -0,0 +1,65 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +std::map sensorStreamMap = { + {OB_SENSOR_IR, OB_STREAM_IR}, + {OB_SENSOR_IR_LEFT, OB_STREAM_IR_LEFT}, + {OB_SENSOR_IR_RIGHT, OB_STREAM_IR_RIGHT} +}; + +int main() try { + // Create a pipeline with default device. + ob::Pipeline pipe; + + // Get the device from pipeline. + std::shared_ptr device = pipe.getDevice(); + + // Get the sensor list from device. + std::shared_ptr sensorList = device->getSensorList(); + + // Create a config for pipeline. + std::shared_ptr config = std::make_shared(); + + for(uint32_t index = 0; index < sensorList->getCount(); index++) { + // Query all supported infrared sensor type and enable the infrared stream. + // For dual infrared device, enable the left and right infrared streams. + // For single infrared device, enable the infrared stream. + OBSensorType sensorType = sensorList->getSensorType(index); + if(sensorType == OB_SENSOR_IR || sensorType == OB_SENSOR_IR_LEFT || sensorType == OB_SENSOR_IR_RIGHT) { + // Enable the stream with specified profile; + config->enableVideoStream(sensorType, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_ANY); + } + } + + pipe.start(config); + + // Create a window for rendering and set the resolution of the window + ob_smpl::CVWindow win("Infrared", 1280, 720, ob_smpl::ARRANGE_ONE_ROW); + while(win.run()) { + // Wait for up to 100ms for a frameset in blocking mode. + auto frameSet = pipe.waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + + // Render a set of frame in the window. + win.pushFramesToView(frameSet); + } + + // Stop the pipeline, no frame data will be generated + pipe.stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.multi_streams/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.multi_streams/CMakeLists.txt new file mode 100644 index 0000000..8156023 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.multi_streams/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_multi_streams) + +add_executable(${PROJECT_NAME} multi_streams.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.multi_streams/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.multi_streams/README.md new file mode 100644 index 0000000..0024ece --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.multi_streams/README.md @@ -0,0 +1,74 @@ +# C++ Sample: 1.stream.multi_streams + +## Overview + +Use SDK to obtain multiple camera data streams and output them + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions. + +Device is a class that can be used to get device information, parameters, and a list of contained sensors. + +Sensor can be used to obtain different components of the camera and the stream of the component, for example, RGB, IR, Depth stream can be obtained through the RGB, IR, Depth sensor. + +Frameset is a combination of different types of Frames. + +## code overview + +1. Configure the output video stream in addition to imu data, such as depth, color, etc. + + ```cpp + // Get sensor list from device. + auto sensorList = device->getSensorList(); + + for(uint32_t i = 0; i < sensorList->getCount(); i++) { + // Get sensor type. + auto sensorType = sensorList->getSensorType(i); + + // exclude gyro and accel sensors. + if(sensorType == OB_SENSOR_GYRO || sensorType == OB_SENSOR_ACCEL) { + continue; + } + + // enable the stream. + config->enableStream(sensorType); + } + + // Start the pipeline with config + std::mutex frameMutex; + std::shared_ptr renderFrameSet; + pipe.start(config, [&](std::shared_ptr frameSet) { + std::lock_guard lock(frameMutex); + renderFrameSet = frameSet; + }); + ``` + +2. Instantiate the pipeline, configure IMU related information and start streaming + + ```cpp + // The IMU frame rate is much faster than the video, so it is advisable to use a separate pipeline to obtain IMU data. + auto dev = pipe.getDevice(); + auto imuPipeline = std::make_shared(dev); + std::mutex imuFrameMutex; + std::shared_ptr renderImuFrameSet; + + std::shared_ptr imuConfig = std::make_shared(); + // enable gyro stream. + imuConfig->enableGyroStream(); + // enable accel stream. + imuConfig->enableAccelStream(); + // start the imu pipeline. + imuPipeline->start(imuConfig, [&](std::shared_ptr frameSet) { + std::lock_guard lockImu(imuFrameMutex); + renderImuFrameSet = frameSet; + }); + ``` + +## Run Sample + +Press the Esc key in the window to exit the program. + +### Result + +![image](../../docs/resource/multistream.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.multi_streams/multi_streams.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.multi_streams/multi_streams.cpp new file mode 100644 index 0000000..568c441 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/1.stream.multi_streams/multi_streams.cpp @@ -0,0 +1,121 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include +#include + +#define IS_ASTRA_MINI_DEVICE(pid) (pid == 0x069d || pid == 0x065b || pid == 0x065e) + +int main(void) try { + + // Create a pipeline with default device. + ob::Pipeline pipe; + + // Configure which streams to enable or disable for the Pipeline by creating a Config. + std::shared_ptr config = std::make_shared(); + + // Enumerate and config all sensors. + auto device = pipe.getDevice(); + + // Get sensor list from device. + auto sensorList = device->getSensorList(); + + bool supportIMU = false; + for(uint32_t i = 0; i < sensorList->getCount(); i++) { + // Get sensor type. + auto sensorType = sensorList->getSensorType(i); + + // exclude gyro and accel sensors. + if(sensorType == OB_SENSOR_GYRO || sensorType == OB_SENSOR_ACCEL) { + supportIMU = true; + continue; + } + + if(IS_ASTRA_MINI_DEVICE(device->getDeviceInfo()->getPid())) { + if(sensorType == OB_SENSOR_COLOR) { + continue; + } + } + + // enable the stream. + config->enableStream(sensorType); + } + + // Start the pipeline with config + std::mutex frameMutex; + std::shared_ptr renderFrameSet; + pipe.start(config, [&](std::shared_ptr frameSet) { + std::lock_guard lock(frameMutex); + renderFrameSet = frameSet; + }); + + if(supportIMU) { + // The IMU frame rate is much faster than the video, so it is advisable to use a separate pipeline to obtain IMU data. + auto dev = pipe.getDevice(); + auto imuPipeline = std::make_shared(dev); + std::mutex imuFrameMutex; + std::shared_ptr renderImuFrameSet; + + std::shared_ptr imuConfig = std::make_shared(); + // enable gyro stream. + imuConfig->enableGyroStream(); + // enable accel stream. + imuConfig->enableAccelStream(); + // start the imu pipeline. + imuPipeline->start(imuConfig, [&](std::shared_ptr frameSet) { + std::lock_guard lockImu(imuFrameMutex); + renderImuFrameSet = frameSet; + }); + + // Create a window for rendering and set the resolution of the window + ob_smpl::CVWindow win("MultiStream", 1280, 720, ob_smpl::ARRANGE_GRID); + while(win.run()) { + std::lock_guard lockImu(imuFrameMutex); + std::lock_guard lock(frameMutex); + + if(renderFrameSet == nullptr || renderImuFrameSet == nullptr) { + continue; + } + // Render camera and imu frameset. + win.pushFramesToView({ renderFrameSet, renderImuFrameSet }); + } + + // Stop the Pipeline, no frame data will be generated. + pipe.stop(); + + if(supportIMU) { + // Stop the IMU Pipeline, no frame data will be generated. + imuPipeline->stop(); + } + } + else { + // Create a window for rendering and set the resolution of the window + ob_smpl::CVWindow win("MultiStream", 1280, 720, ob_smpl::ARRANGE_GRID); + while(win.run()) { + std::lock_guard lock(frameMutex); + + if(renderFrameSet == nullptr) { + continue; + } + // Render camera and imu frameset. + win.pushFramesToView(renderFrameSet); + } + + // Stop the Pipeline, no frame data will be generated. + pipe.stop(); + } + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.control/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.control/CMakeLists.txt new file mode 100644 index 0000000..27a50b5 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.control/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_device_control) + +add_executable(${PROJECT_NAME} device_control.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.control/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.control/README.md new file mode 100644 index 0000000..d457ba6 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.control/README.md @@ -0,0 +1,176 @@ +# C++ Sample:2.device.control + +## Overview + +The SDK can be used to modify camera-related parameters, including laser switch, laser level intensity, white balance switch, etc. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions. + +Device is a class that can be used to get device information, parameters, and a list of contained sensors. + +Sensor can be used to obtain different components of the camera and the stream of the component, for example, RGB, IR, Depth stream can be obtained through the RGB, IR, Depth sensor. + +## code overview + +1. Get camera related information and output + + ```cpp + // select a device to operate + std::shared_ptr device = nullptr; + if(deviceList->getCount() > 0) { + if(deviceList->getCount() <= 1) { + // If a single device is plugged in, the first one is selected by default + device = deviceList->getDevice(0); + } + else { + device = selectDevice(deviceList); + } + auto deviceInfo = device->getDeviceInfo(); + std::cout << "\n------------------------------------------------------------------------\n"; + std::cout << "Current Device: " + << " name: " << deviceInfo->getName() << ", vid: 0x" << std::hex << deviceInfo->getVid() << ", pid: 0x" << std::setw(4) + << std::setfill('0') << deviceInfo->getPid() << ", uid: 0x" << deviceInfo->getUid() << std::dec << std::endl; + } + else { + std::cout << "Device Not Found" << std::endl; + isSelectDevice = false; + break; + } + ``` + +2. Get the relevant parameters stored in the container and reorder them by id + + ```cpp + // Get property list + std::vector getPropertyList(std::shared_ptr device) { + std::vector propertyVec; + propertyVec.clear(); + uint32_t size = device->getSupportedPropertyCount(); + for(uint32_t i = 0; i < size; i++) { + OBPropertyItem property_item = device->getSupportedProperty(i); + if(isPrimaryTypeProperty(property_item) && property_item.permission != OB_PERMISSION_DENY) { + propertyVec.push_back(property_item); + } + } + return propertyVec; + } + ``` + + ```cpp + std::vector propertyList = getPropertyList(device); + std::sort(propertyList.begin(), propertyList.end(), [](const OBPropertyItem &a, const OBPropertyItem &b) { return a.id < b.id; }); + ``` + +3. Use the get command to obtain camera-related property values + + ```cpp + // get property value + void getPropertyValue(std::shared_ptr device, OBPropertyItem propertyItem) { + try { + bool bool_ret = false; + int int_ret = 0; + float float_ret = 0.0f; + + switch(propertyItem.type) { + case OB_BOOL_PROPERTY: + try { + bool_ret = device->getBoolProperty(propertyItem.id); + } + catch(...) { + std::cout << "get bool property failed." << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",get bool value:" << bool_ret << std::endl; + break; + case OB_INT_PROPERTY: + try { + int_ret = device->getIntProperty(propertyItem.id); + } + catch(...) { + std::cout << "get int property failed." << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",get int value:" << int_ret << std::endl; + break; + case OB_FLOAT_PROPERTY: + try { + float_ret = device->getFloatProperty(propertyItem.id); + } + catch(...) { + std::cout << "get float property failed." << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",get float value:" << float_ret << std::endl; + break; + default: + break; + } + } + catch(...) { + std::cout << "get property failed: " << propertyItem.name << std::endl; + } + } + ``` + +4. Use the set command to set camera-related property values + + ```cpp + // set properties + void setPropertyValue(std::shared_ptr device, OBPropertyItem propertyItem, std::string strValue) { + try { + int int_value = 0; + float float_value = 0.0f; + int bool_value = 0; + switch(propertyItem.type) { + case OB_BOOL_PROPERTY: + bool_value = std::atoi(strValue.c_str()); + try { + device->setBoolProperty(propertyItem.id, bool_value); + } + catch(...) { + std::cout << "set bool property fail." << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",set bool value:" << bool_value << std::endl; + break; + case OB_INT_PROPERTY: + int_value = std::atoi(strValue.c_str()); + try { + device->setIntProperty(propertyItem.id, int_value); + } + catch(...) { + std::cout << "set int property fail." << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",set int value:" << int_value << std::endl; + break; + case OB_FLOAT_PROPERTY: + float_value = static_cast(std::atof(strValue.c_str())) ; + try { + device->setFloatProperty(propertyItem.id, float_value); + } + catch(...) { + std::cout << "set float property fail." << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",set float value:" << float_value << std::endl; + break; + default: + break; + } + } + catch(...) { + std::cout << "set property failed: " << propertyItem.name << std::endl; + } + } + ``` + +## Run Sample + +Select the camera you want to operate. If it is a single device, skip the selection. +You can enter the command ? to get all the properties of the camera, including setting the maximum and minimum values, etc. +You can enter set to set command to setto set parameters, for example 6 set 0 (note the space) +You can enter the get command to set parameters, for example, 6 get (note the space) +Press the Esc key in the window to exit the program. + +### Result + +![image](../../docs/resource/control1.jpg) + +![image](../../docs/resource/control2.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.control/device_control.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.control/device_control.cpp new file mode 100644 index 0000000..78fc8f5 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.control/device_control.cpp @@ -0,0 +1,320 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +std::shared_ptr selectDevice(std::shared_ptr deviceList); +std::vector getPropertyList(std::shared_ptr device); +bool isPrimaryTypeProperty(OBPropertyItem propertyItem); +void printfPropertyList(std::shared_ptr device, const std::vector &propertyList); +void setPropertyValue(std::shared_ptr device, OBPropertyItem item, std::string strValue); +void getPropertyValue(std::shared_ptr device, OBPropertyItem item); +std::string permissionTypeToString(OBPermissionType permission); + +int main(void) try { + // Create a Context. + ob::Context context; + + // Query the list of connected devices + auto deviceList = context.queryDeviceList(); + + bool isSelectDevice = true; + while(isSelectDevice) { + // select a device to operate + std::shared_ptr device = nullptr; + if(deviceList->getCount() > 0) { + if(deviceList->getCount() <= 1) { + // If a single device is plugged in, the first one is selected by default + device = deviceList->getDevice(0); + } + else { + device = selectDevice(deviceList); + } + auto deviceInfo = device->getDeviceInfo(); + std::cout << "\n------------------------------------------------------------------------\n"; + std::cout << "Current Device: " + << " name: " << deviceInfo->getName() << ", vid: 0x" << std::hex << deviceInfo->getVid() << ", pid: 0x" << std::setw(4) + << std::setfill('0') << deviceInfo->getPid() << ", uid: 0x" << deviceInfo->getUid() << std::dec << std::endl; + } + else { + std::cout << "Device Not Found" << std::endl; + isSelectDevice = false; + break; + } + + std::cout << "Input \"?\" to get all properties." << std::endl; + + std::vector propertyList = getPropertyList(device); + std::sort(propertyList.begin(), propertyList.end(), [](const OBPropertyItem &a, const OBPropertyItem &b) { return a.id < b.id; }); + + bool isSelectProperty = true; + while(isSelectProperty) { + std::string choice; + std::getline(std::cin, choice); + + if(choice != "?") { + std::istringstream ss(choice); + std::string tmp; + std::vector controlVec; + while(ss >> tmp) { + controlVec.push_back(tmp); + } + + if(controlVec.size() <= 0) + continue; + + // exit the program + if(controlVec.at(0) == "exit") { + isSelectProperty = false; + isSelectDevice = false; + break; + } + + // Check if it matches the input format + if(controlVec.size() <= 1 || (controlVec.at(1) != "get" && controlVec.at(1) != "set") || controlVec.size() > 3 + || (controlVec.at(1) == "set" && controlVec.size() < 3)) { + std::cout << "Property control usage: [property index] [set] [property value] or [property index] [get]" << std::endl; + continue; + } + size_t size = propertyList.size(); + size_t selectId = std::atoi(controlVec.at(0).c_str()); + if(selectId >= size) { + std::cout << "Your selection is out of range, please reselect: " << std::endl; + continue; + } + + bool isGetValue = controlVec.at(1) == "get" ? true : false; + auto propertyItem = propertyList.at(selectId); + + if(isGetValue) { + // get property value + getPropertyValue(device, propertyItem); + } + else { + // set property value + setPropertyValue(device, propertyItem, controlVec.at(2)); + } + } + else { + printfPropertyList(device, propertyList); + std::cout << "Please select property.(Property control usage: [property number] [set/get] [property value])" << std::endl; + } + } + } + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +// Select a device, the name, pid, vid, uid of the device will be printed here, and the corresponding device object will be created after selection +std::shared_ptr selectDevice(std::shared_ptr deviceList) { + int devCount = deviceList->getCount(); + std::cout << "Device list: " << std::endl; + for(int i = 0; i < devCount; i++) { + std::cout << i << ". name: " << deviceList->getName(i) << ", vid: 0x" << std::hex << deviceList->getVid(i) << ", pid: 0x" << std::setw(4) + << std::setfill('0') << deviceList->getPid(i) << ", uid: 0x" << deviceList->getUid(i) << ", sn: " << deviceList->getSerialNumber(i) + << std::dec << std::endl; + } + std::cout << "Select a device: "; + + int devIndex; + std::cin >> devIndex; + while(devIndex < 0 || devIndex >= devCount || std::cin.fail()) { + std::cin.clear(); + std::cin.ignore(); + std::cout << "Your select is out of range, please reselect: " << std::endl; + std::cin >> devIndex; + } + + return deviceList->getDevice(devIndex); +} + +// Print a list of supported properties +void printfPropertyList(std::shared_ptr device, const std::vector &propertyList) { + std::cout << "size: " << propertyList.size() << std::endl; + if(propertyList.empty()) { + std::cout << "No supported property!" << std::endl; + } + std::cout << "\n------------------------------------------------------------------------\n"; + for(size_t i = 0; i < propertyList.size(); i++) { + auto property_item = propertyList[i]; + std::string strRange = ""; + + OBIntPropertyRange int_range; + OBFloatPropertyRange float_range; + switch(property_item.type) { + case OB_BOOL_PROPERTY: + strRange = "Bool value(min:0, max:1, step:1)"; + break; + case OB_INT_PROPERTY: { + try { + int_range = device->getIntPropertyRange(property_item.id); + strRange = "Int value(min:" + std::to_string(int_range.min) + ", max:" + std::to_string(int_range.max) + + ", step:" + std::to_string(int_range.step) + ")"; + } + catch(...) { + std::cout << "get int property range failed." << std::endl; + } + } break; + case OB_FLOAT_PROPERTY: + try { + float_range = device->getFloatPropertyRange(property_item.id); + strRange = "Float value(min:" + std::to_string(float_range.min) + ", max:" + std::to_string(float_range.max) + + ", step:" + std::to_string(float_range.step) + ")"; + } + catch(...) { + std::cout << "get float property range failed." << std::endl; + } + break; + default: + break; + } + + std::cout.setf(std::ios::right); + std::cout.fill('0'); + std::cout.width(2); + std::cout << i << ". "; + std::cout << property_item.name << "(" << (int)property_item.id << ")"; + std::cout << ", permission=" << permissionTypeToString(property_item.permission) << ", range=" << strRange << std::endl; + } + std::cout << "------------------------------------------------------------------------\n"; +} + +bool isPrimaryTypeProperty(OBPropertyItem propertyItem) { + return propertyItem.type == OB_INT_PROPERTY || propertyItem.type == OB_FLOAT_PROPERTY || propertyItem.type == OB_BOOL_PROPERTY; +} + +// Get property list +std::vector getPropertyList(std::shared_ptr device) { + std::vector propertyVec; + propertyVec.clear(); + uint32_t size = device->getSupportedPropertyCount(); + for(uint32_t i = 0; i < size; i++) { + OBPropertyItem property_item = device->getSupportedProperty(i); + if(isPrimaryTypeProperty(property_item) && property_item.permission != OB_PERMISSION_DENY) { + propertyVec.push_back(property_item); + } + } + return propertyVec; +} + +// set properties +void setPropertyValue(std::shared_ptr device, OBPropertyItem propertyItem, std::string strValue) { + try { + int int_value = 0; + float float_value = 0.0f; + int bool_value = 0; + switch(propertyItem.type) { + case OB_BOOL_PROPERTY: + bool_value = std::atoi(strValue.c_str()); + try { + device->setBoolProperty(propertyItem.id, bool_value); + } + catch(ob::Error &e) { + std::cout << "set bool property fail: " << e.what() << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",set bool value:" << bool_value << std::endl; + break; + case OB_INT_PROPERTY: + int_value = std::atoi(strValue.c_str()); + try { + device->setIntProperty(propertyItem.id, int_value); + } + catch(ob::Error &e) { + std::cout << "set int property fail: " << e.what() << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",set int value:" << int_value << std::endl; + break; + case OB_FLOAT_PROPERTY: + float_value = static_cast(std::atof(strValue.c_str())); + try { + device->setFloatProperty(propertyItem.id, float_value); + } + catch(ob::Error &e) { + std::cout << "set floar property fail: " << e.what() << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",set float value:" << float_value << std::endl; + break; + default: + break; + } + } + catch(...) { + std::cout << "set property failed: " << propertyItem.name << std::endl; + } +} + +// get property value +void getPropertyValue(std::shared_ptr device, OBPropertyItem propertyItem) { + try { + bool bool_ret = false; + int int_ret = 0; + float float_ret = 0.0f; + + switch(propertyItem.type) { + case OB_BOOL_PROPERTY: + try { + bool_ret = device->getBoolProperty(propertyItem.id); + } + catch(ob::Error &e) { + std::cout << "get bool property failed: " << e.what() << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",get bool value:" << bool_ret << std::endl; + break; + case OB_INT_PROPERTY: + try { + int_ret = device->getIntProperty(propertyItem.id); + } + catch(ob::Error &e) { + std::cout << "get int property failed: " << e.what() << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",get int value:" << int_ret << std::endl; + break; + case OB_FLOAT_PROPERTY: + try { + float_ret = device->getFloatProperty(propertyItem.id); + } + catch(ob::Error &e) { + std::cout << "get float property failed: " << e.what() << std::endl; + } + std::cout << "property name:" << propertyItem.name << ",get float value:" << float_ret << std::endl; + break; + default: + break; + } + } + catch(...) { + std::cout << "get property failed: " << propertyItem.name << std::endl; + } +} + +std::string permissionTypeToString(OBPermissionType permission) { + switch(permission) { + case OB_PERMISSION_READ: + return "R/_"; + case OB_PERMISSION_WRITE: + return "_/W"; + case OB_PERMISSION_READ_WRITE: + return "R/W"; + + default: + break; + } + return "_/_"; +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.firmware_update/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.firmware_update/CMakeLists.txt new file mode 100644 index 0000000..c451d08 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.firmware_update/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_device_firmware_update) + +add_executable(${PROJECT_NAME} device_firmware_update.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.firmware_update/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.firmware_update/README.md new file mode 100644 index 0000000..94a465c --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.firmware_update/README.md @@ -0,0 +1,108 @@ +# C++ Sample:2.device.firmware_update + +## Overview + +This sample demonstrates how to use the SDK to update the firmware of a connected device. It includes functions to list connected devices, select a device, and update its firmware. + +> Note: This sample are not suiltable for Femto Mega, Femto Mega i, and Femto Bolt devices. +> For these devices, please refer to the this repo:[https://github.com/orbbec/OrbbecFirmware](https://github.com/orbbec/OrbbecFirmware) + +### Knowledge + +Context is the environment context, the first object created during initialization, which can be used to perform some settings, including but not limited to device status change callbacks, log level settings, etc. Context can access multiple Devices. + +Device is the device object, which can be used to obtain the device information, such as the model, serial number, and various sensors.One actual hardware device corresponds to one Device object. + +## code overview + +1. Initialize the SDK Context: This is necessary to access the connected devices. + + ```c++ + std::shared_ptr context = std::make_shared(); + ``` +2. List Connected Devices. + + ```c++ + std::shared_ptr deviceList = context->queryDeviceList(); + for(uint32_t i = 0; i < deviceList->getCount(); ++i) { + devices.push_back(deviceList->getDevice(i)); + } + ``` +3. Define a Callback Function for Firmware Update Progress. + + You can define a callback function to get the progress of the firmware update. The callback function will be called every time the device updates its progress. + + ```c++ + void firmwareUpdateCallback(OBFwUpdateState state, const char *message, uint8_t percent) { + if(firstCall) { + firstCall = !firstCall; + } + else { + std::cout << "\033[3F"; // Move cursor up 3 lines + } + + std::cout << "\033[K"; // Clear the current line + std::cout << "Progress: " << static_cast(percent) << "%" << std::endl; + + std::cout << "\033[K"; + std::cout << "Status : "; + switch(state) { + case STAT_VERIFY_SUCCESS: + std::cout << "Image file verification success" << std::endl; + break; + case STAT_FILE_TRANSFER: + std::cout << "File transfer in progress" << std::endl; + break; + case STAT_DONE: + std::cout << "Update completed" << std::endl; + break; + case STAT_IN_PROGRESS: + std::cout << "Upgrade in progress" << std::endl; + break; + case STAT_START: + std::cout << "Starting the upgrade" << std::endl; + break; + case STAT_VERIFY_IMAGE: + std::cout << "Verifying image file" << std::endl; + break; + default: + std::cout << "Unknown status or error" << std::endl; + break; + } + + std::cout << "\033[K"; + std::cout << "Message : " << message << std::endl << std::flush; + } + ``` + +4. Update the Device Firmware. + + After selecting a device, update its firmware by calling the updateFirmware function with the specified callback. + + ```c++ + devices[deviceIndex]->updateFirmware(firmwarePath.c_str(), firmwareUpdateCallback, false); + ``` + +### Attention + +1. After the firmware update completes, you need to restart the device manually to apply the new firmware. Alternatively, you can use the `reboot()` function to restart the device programmatically. + + ```c++ + device->reboot(); + ``` + +2. Don't plug out the device during the firmware update process. + +3. For linux users, it is recommended to use the `LibUVC` as the backend as the `V4L2` backend may cause some issues on some systems. Switch backend before create device like this: + + ```c++ + context->setUvcBackendType(OB_UVC_BACKEND_TYPE_LIBUVC); + ``` + +## Run Sample + +Select the device for firmware update and input the path of the firmware file. The SDK will start updating the firmware, and the progress will be displayed on the console. + +### Result + +![image](../../docs/resource/device_firmware_update.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.firmware_update/device_firmware_update.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.firmware_update/device_firmware_update.cpp new file mode 100644 index 0000000..0e25907 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.firmware_update/device_firmware_update.cpp @@ -0,0 +1,202 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include + +void firmwareUpdateCallback(OBFwUpdateState state, const char *message, uint8_t percent); +bool getFirmwarePath(std::string &firmwarePath); +bool selectDevice(int &deviceIndex); +void printDeviceList(); + +bool firstCall = true; +std::vector> devices{}; + +int main() try { + // Create a context to access the connected devices + std::shared_ptr context = std::make_shared(); + +#if defined(__linux__) + // On Linux, it is recommended to use the libuvc backend for device access as v4l2 is not always reliable on some systems for firmware update. + context->setUvcBackendType(OB_UVC_BACKEND_TYPE_LIBUVC); +#endif + + // Get connected devices from the context + std::shared_ptr deviceList = context->queryDeviceList(); + if(deviceList->getCount() == 0) { + std::cout << "No device found. Please connect a device first!" << std::endl; + std::cout << "Press any key to exit..." << std::endl; + ob_smpl::waitForKeyPressed(); + return 0; + } + + for(uint32_t i = 0; i < deviceList->getCount(); ++i) { + devices.push_back(deviceList->getDevice(i)); + } + std::cout << "Devices found:" << std::endl; + printDeviceList(); + + while(true) { + firstCall = true; + int deviceIndex = -1; + + if(!selectDevice(deviceIndex)) { + break; + } + + std::string firmwarePath; + if(!getFirmwarePath(firmwarePath)) { + break; + } + + std::cout << "Upgrading device firmware, please wait...\n\n"; + try { + // Set async to false to synchronously block and wait for the device firmware upgrade to complete. + devices[deviceIndex]->updateFirmware(firmwarePath.c_str(), firmwareUpdateCallback, false); + } + catch(ob::Error &e) { + // If the update fails, will throw an exception. + std::cerr << "\nThe upgrade was interrupted! An error occurred! " << std::endl; + std::cerr << "Error message: " << e.what() << std::endl; + std::cout << "Press any key to exit." << std::endl; + ob_smpl::waitForKeyPressed(); + break; + } + + std::string input; + std::cout << "Enter 'Q' or 'q' to quit, or any other key to continue: "; + std::getline(std::cin, input); + if(input == "Q" || input == "q") { + break; + } + } +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +void firmwareUpdateCallback(OBFwUpdateState state, const char *message, uint8_t percent) + +{ + if(firstCall) { + firstCall = !firstCall; + } + else { + std::cout << "\033[3F"; // Move cursor up 3 lines + } + + std::cout << "\033[K"; // Clear the current line + std::cout << "Progress: " << static_cast(percent) << "%" << std::endl; + + std::cout << "\033[K"; + std::cout << "Status : "; + switch(state) { + case STAT_VERIFY_SUCCESS: + std::cout << "Image file verification success" << std::endl; + break; + case STAT_FILE_TRANSFER: + std::cout << "File transfer in progress" << std::endl; + break; + case STAT_DONE: + std::cout << "Update completed" << std::endl; + break; + case STAT_IN_PROGRESS: + std::cout << "Upgrade in progress" << std::endl; + break; + case STAT_START: + std::cout << "Starting the upgrade" << std::endl; + break; + case STAT_VERIFY_IMAGE: + std::cout << "Verifying image file" << std::endl; + break; + default: + std::cout << "Unknown status or error" << std::endl; + break; + } + + std::cout << "\033[K"; + std::cout << "Message : " << message << std::endl << std::flush; +} + +bool getFirmwarePath(std::string &firmwarePath) { + std::cout << "Please input the path of the firmware file (.bin) to be updated:" << std::endl; + std::cout << "(Enter 'Q' or 'q' to quit): " << std::endl; + std::cout << "Path: "; + std::string input; + std::getline(std::cin, input); + + if(input == "Q" || input == "q") { + exit(EXIT_SUCCESS); + } + + // Remove leading and trailing whitespaces + input.erase(std::find_if(input.rbegin(), input.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), input.end()); + + // Remove leading and trailing quotes + if(!input.empty() && input.front() == '\'' && input.back() == '\'') { + input = input.substr(1, input.size() - 2); + } + + if(input.size() > 4 && (input.substr(input.size() - 4) == ".bin" || input.substr(input.size() - 4) == ".img")) { + firmwarePath = input; + std::cout << "Firmware file confirmed: " << firmwarePath << std::endl << std::endl; + return true; + } + + std::cout << "Invalid file format. Please provide a .bin file." << std::endl << std::endl; + return getFirmwarePath(firmwarePath); +} + +void printDeviceList() { + std::cout << "--------------------------------------------------------------------------------\n"; + for(uint32_t i = 0; i < devices.size(); ++i) { + std::cout << "[" << i << "] " << "Device: " << devices[i]->getDeviceInfo()->getName(); + std::cout << " | SN: " << devices[i]->getDeviceInfo()->getSerialNumber(); + std::cout << " | Firmware version: " << devices[i]->getDeviceInfo()->getFirmwareVersion() << std::endl; + } + std::cout << "---------------------------------------------------------------------------------\n"; +} + +bool selectDevice(int &deviceIndex) { + std::string input; + while(true) { + std::cout << "Please select a device to update the firmware, enter 'l' to list devices, or enter 'q' to quit: " << std::endl; + std::cout << "Device index: "; + std::getline(std::cin, input); + + if(input == "Q" || input == "q") { + return false; + } + + if(input == "l" || input == "L") { + printDeviceList(); + continue; + } + + try { + deviceIndex = std::stoi(input); + if(deviceIndex < 0 || deviceIndex >= static_cast(devices.size())) { + std::cout << "Invalid input, please enter a valid index number." << std::endl; + continue; + } + std::cout << std::endl; + break; + } + catch(...) { + std::cout << "Invalid input, please enter a valid index number." << std::endl; + continue; + } + } + return true; +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.forceip/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.forceip/CMakeLists.txt new file mode 100644 index 0000000..d394113 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.forceip/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_device_forceip) + +add_executable(${PROJECT_NAME} forceip.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.forceip/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.forceip/README.md new file mode 100644 index 0000000..e6c5a33 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.forceip/README.md @@ -0,0 +1,53 @@ +# C++ Sample: 2.device.forceip + +## Overview +This sample demonstrates how to use the SDK context class to query connected devices, configure the network IP of a selected device using the ForceIP command (as defined by the GigE Vision standard) + +### Knowledge +The Context class serves as the entry point to the SDK. It provides functionality to: +1. Query connected device lists +2. Modify network configurations for the selected device + +## Code Overview + +1. Query device list and select a device + + ```cpp + // Create a Context object to interact with Orbbec devices + ob::Context context; + // Query the list of connected devices + auto deviceList = context.queryDeviceList(); + // Select a device to operate + uint32_t selectedIndex; + auto res = selectDevice(deviceList, selectedIndex); + ``` + +2. Get new IP configuration from user input + + ```cpp + OBNetIpConfig config = getIPConfig(); + ``` + +3. Change the selected device IP configuration and print the result of the operation. + + ```cpp + res = context.forceIp(deviceList->getUid(deviceNumber), config); + if(res) { + std::cout << "The new IP configuration has been successfully applied to the device." << std::endl; + } + else { + std::cout << "Failed to apply the new IP configuration." << std::endl; + } + ``` + +## Run Sample +Device list: +Enter your choice: +Please enter the network configuration information: +Enter IP address: +Enter Subnet Mask: +Enter Gateway address: +The new IP configuration has been successfully applied to the device. + +### Result +![result](/docs/resource/forceip.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.forceip/forceip.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.forceip/forceip.cpp new file mode 100644 index 0000000..7318905 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.forceip/forceip.cpp @@ -0,0 +1,175 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include +#include + +static bool parseIpString(const std::string &Str, uint8_t *out) { + if(Str.empty()) { + return false; + } + + try { + std::istringstream ss(Str); + std::string token; + int count = 0; + while(std::getline(ss, token, '.')) { + if(count > 4) { + return false; + } + for(char c: token) { + if(!isdigit(c)) { + return false; + } + } + + int val = std::stoi(token); + if(val < 0 || val > 255) { + return false; + } + out[count++] = static_cast(val); + } + return count == 4; + } + catch(const std::exception &e) { + // error + (void)e; + } + return false; +} + +static bool selectDevice(std::shared_ptr deviceList, uint32_t &selectedIndex) { + selectedIndex = static_cast(-1); + + auto devCount = deviceList->getCount(); + if(devCount == 0) { + std::cout << "No devices found." << std::endl; + return false; + } + + std::vector indexList; + uint32_t count = 0; + + std::cout << "Ethernet device list:" << std::endl; + for(uint32_t i = 0; i < devCount; i++) { + std::string DeviceConnectType = deviceList->getConnectionType(i); + if(DeviceConnectType != "Ethernet") { + continue; + } + std::cout << count << ". Name: " << deviceList->getName(i) << ", Serial Number: " << deviceList->getSerialNumber(i) + << ", MAC: " << deviceList->getUid(i) << std::dec << ", IP: " << deviceList->getIpAddress(i) + << ", Subnet Mask: " << deviceList->getSubnetMask(i) << ", Gateway: " << deviceList->getGateway(i) << std::endl; + indexList.push_back(i); + count++; + } + if(indexList.empty()) { + std::cout << "No network devices found." << std::endl; + return false; + } + + uint32_t index; + do { + std::cout << "Enter your choice: "; + std::cin >> index; + if(std::cin.fail()) { + std::cin.clear(); + std::cin.ignore(); + std::cout << "Invalid input, please enter a number." << std::endl; + continue; + } + + if(index >= indexList.size()) { + std::cout << "Invalid input, please enter a valid index number." << std::endl; + continue; + } + selectedIndex = indexList[index]; + return true; + } while(true); + return false; +} + +static OBNetIpConfig getIPConfig() { + OBNetIpConfig cfg; + std::string val; + uint8_t address[4]; + uint8_t mask[4]; + uint8_t gateway[4]; + + std::cout << "Please enter the network configuration information:" << std::endl; + std::cout << "Enter IP address:" << std::endl; + while(std::cin >> val) { + if(parseIpString(val, address)) { + break; + } + std::cout << "Invalid format." << std::endl; + std::cout << "Enter IP address:" << std::endl; + } + + std::cout << "Enter Subnet Mask:" << std::endl; + while(std::cin >> val) { + if(parseIpString(val, mask)) { + break; + } + std::cout << "Invalid format." << std::endl; + std::cout << "Enter Subnet Mask:" << std::endl; + } + + std::cout << "Enter Gateway address:" << std::endl; + while(std::cin >> val) { + if(parseIpString(val, gateway)) { + break; + } + std::cout << "Invalid format." << std::endl; + std::cout << "Enter Gateway address:" << std::endl; + } + + cfg.dhcp = 0; + for(int i = 0; i < 4; ++i) { + cfg.address[i] = address[i]; + cfg.gateway[i] = gateway[i]; + cfg.mask[i] = mask[i]; + } + return cfg; +} + +int main(void) try { + // Create a Context object to interact with Orbbec devices + ob::Context context; + // Query the list of connected devices + auto deviceList = context.queryDeviceList(); + // Select a device to operate + uint32_t selectedIndex; + auto res = selectDevice(deviceList, selectedIndex); + if(res) { + // Get the new IP configuration from user input + OBNetIpConfig config = getIPConfig(); + + // Change device IP configuration + res = context.forceIp(deviceList->getUid(selectedIndex), config); + if(res) { + std::cout << "The new IP configuration has been successfully applied to the device." << std::endl; + } + else { + std::cout << "Failed to apply the new IP configuration." << std::endl; + } + } + + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + return 0; +} +catch(ob::Error &e) { + std::cerr << "Function: " << e.getFunction() << "\nArguments: " << e.getArgs() << "\nMessage: " << e.what() << "\nException Type: " << e.getExceptionType() + << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.hot_plugin/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.hot_plugin/CMakeLists.txt new file mode 100644 index 0000000..85792a4 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.hot_plugin/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_hot_plugin) + +add_executable(${PROJECT_NAME} hot_plugin.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.hot_plugin/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.hot_plugin/README.md new file mode 100644 index 0000000..c5546a4 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.hot_plugin/README.md @@ -0,0 +1,74 @@ +# C++ Sample:2.device.hot_plugin + +## Overview + +Use SDK to handle the settings of device unplug callback and process the acquired code stream after unplugging + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions. + +Device is a class that can be used to get device information, parameters, and a list of contained sensors. + +Sensor can be used to obtain different components of the camera and the stream of the component, for example, RGB, IR, Depth stream can be obtained through the RGB, IR, Depth sensor. + +### Attention + +*The GMSL devices (such as Gemini335Lg) do not support hot plugging.* + +## code overview + +1. Register device callback and execute relevant functions during device unplugging and unplugging + + ```cpp + ctx.setDeviceChangedCallback( []( std::shared_ptr< ob::DeviceList > removedList, std::shared_ptr< ob::DeviceList > addedList ) { + DeviceDisconnectCallback( removedList ); + DeviceConnectCallback( addedList ); + } ); + ``` + +2. Trigger the callback function to print relevant information + + ```cpp + void printDeviceList(const std::string &prompt, std::shared_ptr deviceList) { + auto count = deviceList->getCount(); + if(count == 0) { + return; + } + std::cout << count << " device(s) " << prompt << ": " << std::endl; + for(uint32_t i = 0; i < count; i++) { + auto uid = deviceList->getUid(i); + auto vid = deviceList->getVid(i); + auto pid = deviceList->getPid(i); + auto serialNumber = deviceList->getSerialNumber(i); + auto connection = deviceList->getConnectionType(i); + std::cout << " - uid: " << uid << ", vid: 0x" << std::hex << std::setfill('0') << std::setw(4) << vid << ", pid: 0x" << pid + << ", serial number: " << serialNumber << ", connection: " << connection << std::endl; + } + std::cout << std::endl; + } + ``` + +3. Restart your device + +```cpp + void rebootDevices(std::shared_ptr deviceList) { + for(uint32_t i = 0; i < deviceList->getCount(); i++) { + // get device from device list + auto device = deviceList->getDevice(i); + + // reboot device + device->reboot(); + } + } +``` + +## Run Sample + +Press R to reboot the device +You can try to manually unplug and plug the device +Press the Esc key in the window to exit the program + +### Result + +![image](../../docs/resource/hotplugin.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.hot_plugin/hot_plugin.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.hot_plugin/hot_plugin.cpp new file mode 100644 index 0000000..8b52adb --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.hot_plugin/hot_plugin.cpp @@ -0,0 +1,80 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include +#include + +void printDeviceList(const std::string &prompt, std::shared_ptr deviceList) { + auto count = deviceList->getCount(); + if(count == 0) { + return; + } + std::cout << count << " device(s) " << prompt << ": " << std::endl; + for(uint32_t i = 0; i < count; i++) { + auto uid = deviceList->getUid(i); + auto vid = deviceList->getVid(i); + auto pid = deviceList->getPid(i); + auto serialNumber = deviceList->getSerialNumber(i); + auto connection = deviceList->getConnectionType(i); + std::cout << " - uid: " << uid << ", vid: 0x" << std::hex << std::setfill('0') << std::setw(4) << vid << ", pid: 0x" << std::setw(4) << pid + << ", serial number: " << serialNumber << ", connection: " << connection << std::endl; + } + std::cout << std::endl; +} + +void rebootDevices(std::shared_ptr deviceList) { + for(uint32_t i = 0; i < deviceList->getCount(); i++) { + // get device from device list + auto device = deviceList->getDevice(i); + + // reboot device + device->reboot(); + } +} + +int main(void) try { + // create context + ob::Context ctx; + + // register device callback + ctx.setDeviceChangedCallback([](std::shared_ptr removedList, std::shared_ptr deviceList) { + printDeviceList("added", deviceList); + printDeviceList("removed", removedList); + }); + + // query current device list + auto currentList = ctx.queryDeviceList(); + printDeviceList("connected", currentList); + + std::cout << "Press 'r' to reboot the connected devices to trigger the device disconnect and reconnect event, or manually unplug and plugin the device." + << std::endl; + std::cout << "Press 'Esc' to exit." << std::endl << std::endl; + + // main loop, wait for key press + while(true) { + auto key = ob_smpl::waitForKeyPressed(100); + // Press the esc key to exit + if(key == 27) { + break; + } + else if(key == 'r' || key == 'R') { + // update device list + currentList = ctx.queryDeviceList(); + + std::cout << "Rebooting devices..." << std::endl; + rebootDevices(currentList); + } + } + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.multi_devices_firmware_update/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.multi_devices_firmware_update/CMakeLists.txt new file mode 100644 index 0000000..89cd1ec --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.multi_devices_firmware_update/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_multi_devices_firmware_update) + +add_executable(${PROJECT_NAME} multi_devices_firmware_update.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.multi_devices_firmware_update/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.multi_devices_firmware_update/README.md new file mode 100644 index 0000000..a8b32bd --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.multi_devices_firmware_update/README.md @@ -0,0 +1,133 @@ +# C++ Sample:2.multi_devices_firmware_update + +## Overview + +If you want to upgrade multiple Orbbec cameras connected to your system, this sample might be helpful for you. For detailed information about firmware upgrades, please refer to the [2.device.firmware_update](../2.device.firmware_update/README.md). + +> Note: This sample are not suiltable for Femto Mega, Femto Mega i, and Femto Bolt devices. +> For these devices, please refer to the this repo:[https://github.com/orbbec/OrbbecFirmware](https://github.com/orbbec/OrbbecFirmware) + +### Knowledge + +Context is the environment context, the first object created during initialization, which can be used to perform some settings, including but not limited to device status change callbacks, log level settings, etc. Context can access multiple Devices. + +Device is the device object, which can be used to obtain the device information, such as the model, serial number, and various sensors.One actual hardware device corresponds to one Device object. + +## code overview + +1. Initialize the SDK Context: This is necessary to access the connected devices. + + ```c++ + std::shared_ptr context = std::make_shared(); + ``` +2. List Connected Devices. + + ```c++ + std::shared_ptr deviceList = context->queryDeviceList(); + for(uint32_t i = 0; i < deviceList->getCount(); ++i) { + devices.push_back(deviceList->getDevice(i)); + } + ``` +3. Update each device. + + You don't need to worry about issues caused by using incorrect firmware during the upgrade process. The SDK performs internal verification of the firmware to ensure its compatibility and validity. + + ```c++ + for(uint32_t i = 0; i < totalDevices.size(); ++i) { + try { + std::cout << "\nUpgrading device: " << i + 1 << "/" << totalDevices.size() + << " - " << totalDevices[i]->getDeviceInfo()->getName() << std::endl; + + totalDevices[i]->updateFirmware(firmwarePath.c_str(), firmwareUpdateCallback, false); + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + } + } + ``` + +4. Retrieve Status from the Callback + + ```c++ + void firmwareUpdateCallback(OBFwUpdateState state, const char *message, uint8_t percent) { + if(firstCall) { + firstCall = !firstCall; + } + else { + std::cout << "\033[3F"; // Move cursor up 3 lines + } + + std::cout << "\033[K"; // Clear the current line + std::cout << "Progress: " << static_cast(percent) << "%" << std::endl; + + std::cout << "\033[K"; + std::cout << "Status : "; + switch(state) { + case STAT_VERIFY_SUCCESS: + std::cout << "Image file verification success" << std::endl; + break; + case STAT_FILE_TRANSFER: + std::cout << "File transfer in progress" << std::endl; + break; + case STAT_DONE: + std::cout << "Update completed" << std::endl; + break; + case STAT_IN_PROGRESS: + std::cout << "Upgrade in progress" << std::endl; + break; + case STAT_START: + std::cout << "Starting the upgrade" << std::endl; + break; + case STAT_VERIFY_IMAGE: + std::cout << "Verifying image file" << std::endl; + break; + case ERR_MISMATCH: + std::cout << "Mismatch between device and image file" << std::endl; + break; + default: + std::cout << "Unknown status or error" << std::endl; + break; + } + + std::cout << "\033[K"; + std::cout << "Message : " << message << std::endl << std::flush; + + if(state == STAT_DONE) { + finalSuccess = true; + finalFailure = false; + } + else if(state == ERR_MISMATCH) { + // If the device's firmware version does not match the image file, the callback status will be ERR_MISMATCH. + finalMismatch = true; + } + else if(state < 0) { + // While state < 0, it means an error occurred. + finalFailure = true; + } + } + ``` + +### Attention + +1. After the firmware update completes, you need to restart the device manually to apply the new firmware. Alternatively, you can use the `reboot()` function to restart the device programmatically. + + ```c++ + device->reboot(); + ``` + +2. Don't plug out the device during the firmware update process. + +3. For linux users, it is recommended to use the `LibUVC` as the backend as the `V4L2` backend may cause some issues on some systems. Switch backend before create device like this: + + ```c++ + context->setUvcBackendType(OB_UVC_BACKEND_TYPE_LIBUVC); + ``` + +## Run Sample + +By providing the firmware file path via the command line, the program will automatically upgrade the devices that match the firmware. + +### Result + +![image](../../docs/resource/multi_devices_firmware_update.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.multi_devices_firmware_update/multi_devices_firmware_update.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.multi_devices_firmware_update/multi_devices_firmware_update.cpp new file mode 100644 index 0000000..789b818 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.multi_devices_firmware_update/multi_devices_firmware_update.cpp @@ -0,0 +1,224 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include + +bool getFirmwarePathFromCommandLine(int argc, char **argv, std::string &firmwarePath); +void firmwareUpdateCallback(OBFwUpdateState state, const char *message, uint8_t percent); +void printDeviceList(); + +bool firstCall = true; +bool finalSuccess = false; +bool finalMismatch = false; +bool finalFailure = false; + +std::vector> totalDevices{}; +std::vector> successDevices{}; +std::vector> misMatchDevices{}; +std::vector> failedDevices{}; + +int main(int argc, char *argv[]) try { + std::string firmwarePath; + if(!getFirmwarePathFromCommandLine(argc, argv, firmwarePath)) { + std::cout << "Press any key to exit..." << std::endl; + exit(EXIT_FAILURE); + } + + // Create a context to access the devices + std::shared_ptr context = std::make_shared(); + +#if defined(__linux__) + // On Linux, it is recommended to use the libuvc backend for device access as v4l2 is not always reliable on some systems for firmware update. + context->setUvcBackendType(OB_UVC_BACKEND_TYPE_LIBUVC); +#endif + + // Query the device list + std::shared_ptr deviceList = context->queryDeviceList(); + if(deviceList->getCount() == 0) { + std::cout << "No device found. Please connect a device first!" << std::endl; + std::cout << "Press any key to exit..." << std::endl; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); + } + + // Get all devices + for(uint32_t i = 0; i < deviceList->getCount(); ++i) { + totalDevices.push_back(deviceList->getDevice(i)); + } + printDeviceList(); + + for(uint32_t i = 0; i < totalDevices.size(); ++i) { + firstCall = true; + finalSuccess = false; + finalMismatch = false; + finalFailure = false; + + try { + std::cout << "\nUpgrading device: " << i + 1 << "/" << totalDevices.size() + << " - " << totalDevices[i]->getDeviceInfo()->getName() << std::endl; + + // Upgrade each device with async set to false for synchronous calls. + // You can set a callback function to retrieve the device's upgrade progress and related information in real time. + totalDevices[i]->updateFirmware(firmwarePath.c_str(), firmwareUpdateCallback, false); + } + catch(ob::Error &e) { + // Unexpected situations, such as device disconnection, will typically throw an exception. + // Note that common issues like verification failures are usually reported through the callback status. + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + finalFailure = true; + } + + if(finalSuccess) { + successDevices.push_back(totalDevices[i]); + } + else if(finalMismatch) { + misMatchDevices.push_back(totalDevices[i]); + } + else if(finalFailure) { + failedDevices.push_back(totalDevices[i]); + } + } + + std::cout << "\nUpgrade Summary:\n"; + std::cout << "==================================================\n"; + + std::cout << "Success (" << successDevices.size() << "):\n"; + for(const auto &device: successDevices) { + std::cout << " - Name: " << device->getDeviceInfo()->getName() << std::endl; + } + + std::cout << "\nMismatch (" << misMatchDevices.size() << "):\n"; + for(const auto &device: misMatchDevices) { + std::cout << " - Name: " << device->getDeviceInfo()->getName() << std::endl; + } + if (misMatchDevices.size() > 0) { + std::cout << "Please check use the correct firmware version and retry the upgrade." << std::endl; + } + + std::cout << "\nFailure (" << failedDevices.size() << "):\n"; + for(const auto &device: failedDevices) { + std::cout << " - Name: " << device->getDeviceInfo()->getName() << std::endl; + } + + std::cout << "\nUpgrade process completed. Try to reboot all successfully upgraded devices." << std::endl; + for (auto &device : successDevices) { + device->reboot(); + } + + std::cout << "Press any key to exit..." << std::endl; + ob_smpl::waitForKeyPressed(); + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +void firmwareUpdateCallback(OBFwUpdateState state, const char *message, uint8_t percent) { + if(firstCall) { + firstCall = !firstCall; + } + else { + std::cout << "\033[3F"; // Move cursor up 3 lines + } + + std::cout << "\033[K"; // Clear the current line + std::cout << "Progress: " << static_cast(percent) << "%" << std::endl; + + std::cout << "\033[K"; + std::cout << "Status : "; + switch(state) { + case STAT_VERIFY_SUCCESS: + std::cout << "Image file verification success" << std::endl; + break; + case STAT_FILE_TRANSFER: + std::cout << "File transfer in progress" << std::endl; + break; + case STAT_DONE: + std::cout << "Update completed" << std::endl; + break; + case STAT_IN_PROGRESS: + std::cout << "Upgrade in progress" << std::endl; + break; + case STAT_START: + std::cout << "Starting the upgrade" << std::endl; + break; + case STAT_VERIFY_IMAGE: + std::cout << "Verifying image file" << std::endl; + break; + case ERR_MISMATCH: + std::cout << "Mismatch between device and image file" << std::endl; + break; + default: + std::cout << "Unknown status or error" << std::endl; + break; + } + + std::cout << "\033[K"; + std::cout << "Message : " << message << std::endl << std::flush; + + if(state == STAT_DONE) { + finalSuccess = true; + finalFailure = false; + } + else if(state == ERR_MISMATCH) { + // If the device's firmware version does not match the image file, the callback status will be ERR_MISMATCH. + finalMismatch = true; + } + else if(state < 0) { + // While state < 0, it means an error occurred. + finalFailure = true; + } +} + +bool getFirmwarePathFromCommandLine(int argc, char **argv, std::string &firmwarePath) { + if(argc != 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + std::cerr << "Example: " << argv[0] << " /path/to/firmware.bin" << std::endl; + return false; + } + + std::vector validExtensions = { ".bin", ".img" }; + firmwarePath = argv[1]; + + if(firmwarePath.size() > 4) { + std::string extension = firmwarePath.substr(firmwarePath.size() - 4); + + auto result = std::find_if(validExtensions.begin(), validExtensions.end(), + [extension](const std::string &validExtension) { return extension == validExtension; }); + if(result != validExtensions.end()) { + std::cout << "Firmware file confirmed: " << firmwarePath << std::endl << std::endl; + return true; + } + } + + std::cout << "Invalid input file: Please provide a valid firmware file, supported formats: "; + for(const auto &ext: validExtensions) { + std::cout << ext << " "; + } + std::cout << std::endl; + return false; +} + +void printDeviceList() { + std::cout << "Devices found:" << std::endl; + std::cout << "--------------------------------------------------------------------------------\n"; + for(uint32_t i = 0; i < totalDevices.size(); ++i) { + std::cout << "[" << i << "] " << "Device: " << totalDevices[i]->getDeviceInfo()->getName(); + std::cout << " | SN: " << totalDevices[i]->getDeviceInfo()->getSerialNumber(); + std::cout << " | Firmware version: " << totalDevices[i]->getDeviceInfo()->getFirmwareVersion() << std::endl; + } + std::cout << "---------------------------------------------------------------------------------\n"; +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.optional_depth_presets_update/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.optional_depth_presets_update/CMakeLists.txt new file mode 100644 index 0000000..ace64ee --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.optional_depth_presets_update/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_device_optional_depth_presets_update) + +add_executable(${PROJECT_NAME} device.optional_depth_presets_update.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.optional_depth_presets_update/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.optional_depth_presets_update/README.md new file mode 100644 index 0000000..921ed09 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.optional_depth_presets_update/README.md @@ -0,0 +1,63 @@ +# C++ Sample:2.device.optional_depth_presets_update + +## Overview + +This sample demonstrates how to use the SDK to update the optional depth presets of a connected device. It includes functions to list connected devices, select a device, and update its depth presets. + +> Note: This sample is only applicable to devices that support presets, such as G330 serials of devices + +### Knowledge + +Context is the environment context, the first object created during initialization, which can be used to perform some settings, including but not limited to device status change callbacks, log level settings, etc. Context can access multiple Devices. + +Device is the device object, which can be used to obtain the device information, such as the model, serial number, and various sensors.One actual hardware device corresponds to one Device object. + +## code overview + +1. Initialize the SDK Context: This is necessary to access the connected devices. + + ```c++ + std::shared_ptr context = std::make_shared(); + ``` +2. List Connected Devices. + + ```c++ + std::shared_ptr deviceList = context->queryDeviceList(); + for(uint32_t i = 0; i < deviceList->getCount(); ++i) { + devices.push_back(deviceList->getDevice(i)); + } + ``` +3. Define a Callback Function for Firmware Update Progress. + + You can define a callback function to get the progress of the firmware update. The callback function will be called every time the device updates its progress. + + ```c++ + void presetUpdateCallback(OBFwUpdateState state, const char *message, uint8_t percent) { + // show update state and message here + } + ``` + +4. Update the optional depth presets. + + After selecting a device, update its presets by calling the updateOptionalDepthPresets function with the specified callback. + + ```c++ + device->updateOptionalDepthPresets(filePaths, count, presetUpdateCallback); + ``` + > Note: The api supports upgrading multiple presets at once. For G300 series devices, a maximum of 3 presets can be written at a time. The first preset written will be set as the default preset. + +### Attention + +1. After the optional depth presets update completes, you don't need to restart the device. + +2. Don't plug out the device during the presets update process. + + + +## Run Sample + +Select the device for presets update and input the path of the presets file. The SDK will start updating the presets, and the progress will be displayed on the console. + +### Result + +![image](../../docs/resource/device_optional_depth_presets_update.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.optional_depth_presets_update/device.optional_depth_presets_update.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.optional_depth_presets_update/device.optional_depth_presets_update.cpp new file mode 100644 index 0000000..956d1b8 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.optional_depth_presets_update/device.optional_depth_presets_update.cpp @@ -0,0 +1,295 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include + +static bool shouldContinue(); +static void presetUpdateCallback(bool firstCall, OBFwUpdateState state, const char *message, uint8_t percent); +static bool getPresetPath(std::vector &pathList); +static bool selectDevice(std::shared_ptr &device); +static void printDeviceList(); +static bool isPresetSupported(std::shared_ptr device); +static void printPreset(std::shared_ptr device); + +std::vector> devices{}; + +int main() try { + // Create a context to access the connected devices + std::shared_ptr context = std::make_shared(); + + // Get connected devices from the context + std::shared_ptr deviceList = context->queryDeviceList(); + if(deviceList->getCount() == 0) { + std::cout << "No device found. Please connect a device first!" << std::endl; + std::cout << "Press any key to exit..." << std::endl; + ob_smpl::waitForKeyPressed(); + return 0; + } + + for(uint32_t i = 0; i < deviceList->getCount(); ++i) { + devices.push_back(deviceList->getDevice(i)); + } + std::cout << "Devices found:" << std::endl; + printDeviceList(); + + while(true) { + bool firstCall = true; + OBFwUpdateState updateState = STAT_START; + std::shared_ptr device = nullptr; + + if(!selectDevice(device)) { + break; + } + + printPreset(device); + + std::vector pathList; + if(!getPresetPath(pathList)) { + break; + } + + uint8_t index = 0; + uint8_t count = static_cast(pathList.size()); + char(*filePaths)[OB_PATH_MAX] = new char[count][OB_PATH_MAX]; + + // copy paths + std::cout << "\nPreset file paths you input: " << std::endl; + for(const auto &path: pathList) { + strcpy(filePaths[index++], path.c_str()); + std::cout << "Path " << (uint32_t)index << ": " << path << std::endl; + } + std::cout << std::endl; + + std::cout << "Start to update optional depth preset, please wait a moment...\n\n"; + try { + device->updateOptionalDepthPresets(filePaths, count, [&updateState, &firstCall](OBFwUpdateState state, const char *message, uint8_t percent) { + updateState = state; + presetUpdateCallback(firstCall, state, message, percent); + firstCall = false; + }); + delete[] filePaths; + filePaths = nullptr; + } + catch(ob::Error &e) { + // If the update fails, will throw an exception. + std::cerr << "\nThe update was interrupted! An error occurred! " << std::endl; + std::cerr << "Error message: " << e.what() << "\n" << std::endl; + std::cout << "Press any key to exit." << std::endl; + ob_smpl::waitForKeyPressed(); + delete[] filePaths; + filePaths = nullptr; + break; + } + + std::cout << std::endl; + if(updateState == STAT_DONE || updateState == STAT_DONE_WITH_DUPLICATES) { + // success + std::cout << "After updating the preset: " << std::endl; + printPreset(device); + } + + if(!shouldContinue()) { + break; + } + } +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +static bool shouldContinue() { + std::string input; + std::cout << "Enter 'Q' or 'q' to quit, or any other key to continue: "; + std::getline(std::cin, input); + if(input == "Q" || input == "q") { + return false; + } + return true; +} + +static void presetUpdateCallback(bool firstCall, OBFwUpdateState state, const char *message, uint8_t percent) { + if(!firstCall) { + std::cout << "\033[3F"; // Move cursor up 3 lines + } + + std::cout << "\033[K"; // Clear the current line + std::cout << "Progress: " << static_cast(percent) << "%" << std::endl; + + std::cout << "\033[K"; + std::cout << "Status : "; + switch(state) { + case STAT_VERIFY_SUCCESS: + std::cout << "Image file verification success" << std::endl; + break; + case STAT_FILE_TRANSFER: + std::cout << "File transfer in progress" << std::endl; + break; + case STAT_DONE: + std::cout << "Update completed" << std::endl; + break; + case STAT_DONE_WITH_DUPLICATES: + std::cout << "Update completed, duplicated presets have been ignored" << std::endl; + break; + case STAT_IN_PROGRESS: + std::cout << "Update in progress" << std::endl; + break; + case STAT_START: + std::cout << "Starting the update" << std::endl; + break; + case STAT_VERIFY_IMAGE: + std::cout << "Verifying image file" << std::endl; + break; + default: + std::cout << "Unknown status or error" << std::endl; + break; + } + + std::cout << "\033[K"; + std::cout << "Message : " << message << std::endl << std::flush; +} + +static bool getPresetPath(std::vector &pathList) { + std::cout << "Please input the file paths of the optional depth preset file (.bin):" << std::endl; + std::cout << " - Press 'Enter' to finish this input" << std::endl; + std::cout << " - Press 'Q' or 'q' to exit the program" << std::endl; + + uint8_t count = 0; + + pathList.clear(); + do { + std::cout << "Enter Path: "; + std::string input; + std::getline(std::cin, input); + + if(input == "Q" || input == "q") { + return false; + } + if(input.empty()) { + if(pathList.size() == 0) { + std::cout << "You didn't input any file paths" << std::endl; + if(!shouldContinue()) { + return false; + } + continue; + } + break; + } + + // Remove leading and trailing whitespaces + input.erase(std::find_if(input.rbegin(), input.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), input.end()); + + // Remove leading and trailing quotes + if(!input.empty() && input.front() == '\'' && input.back() == '\'') { + input = input.substr(1, input.size() - 2); + } + + if(input.size() > 4 && input.substr(input.size() - 4) == ".bin") { + pathList.push_back(input); + ++count; + continue; + } + else { + std::cout << "Invalid file format. Please provide a .bin file." << std::endl << std::endl; + continue; + } + } while(count < 10); + + return true; +} + +static bool selectDevice(std::shared_ptr &device) { + std::string input; + + device = nullptr; + while(true) { + std::cout << "Please select a device to update the optional depth preset, enter 'l' to list devices, or enter 'q' to quit: " << std::endl; + std::cout << "Device index: "; + std::getline(std::cin, input); + + if(input == "Q" || input == "q") { + return false; + } + + if(input == "l" || input == "L") { + printDeviceList(); + continue; + } + + try { + uint32_t index = std::stoi(input); + if(index >= static_cast(devices.size())) { + std::cout << "Invalid input, please enter a valid index number." << std::endl; + continue; + } + + device = devices[index]; + if(!isPresetSupported(device)) { + std::cerr << "The device you selected does not support preset. Please select another one" << std::endl; + continue; + } + std::cout << std::endl; + break; + } + catch(...) { + std::cout << "Invalid input, please enter a valid index number." << std::endl; + continue; + } + } + return true; +} + +static void printDeviceList() { + std::cout << "--------------------------------------------------------------------------------\n"; + for(uint32_t i = 0; i < devices.size(); ++i) { + std::cout << "[" << i << "] " << "Device: " << devices[i]->getDeviceInfo()->getName(); + std::cout << " | SN: " << devices[i]->getDeviceInfo()->getSerialNumber(); + std::cout << " | Firmware version: " << devices[i]->getDeviceInfo()->getFirmwareVersion() << std::endl; + } + std::cout << "---------------------------------------------------------------------------------\n"; +} + +static bool isPresetSupported(std::shared_ptr device) { + auto presetList = device->getAvailablePresetList(); + if(presetList && presetList->getCount() > 0) { + return true; + } + return false; +} + +static void printPreset(std::shared_ptr device) { + try { + auto presetList = device->getAvailablePresetList(); + std::cout << "Preset count: " << presetList->getCount() << std::endl; + for(uint32_t i = 0; i < presetList->getCount(); ++i) { + std::cout << " - " << presetList->getName(i) << std::endl; + } + std::cout << "Current preset: " << device->getCurrentPresetName() << "\n" << std::endl; + } + catch(ob::Error &e) { + // If the update fails, will throw an exception. + std::cerr << "\nThe device doesn't support preset! " << std::endl; + std::cerr << "error: " << e.what() << "\n" << std::endl; + return; + } + + std::string key = "PresetVer"; + if(device->isExtensionInfoExist(key)) { + std::string value = device->getExtensionInfo(key); + std::cout << "Preset version: " << value << "\n" << std::endl; + } + else { + std::cout << "PresetVer: n/a\n" << std::endl; + } +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.playback/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.playback/CMakeLists.txt new file mode 100644 index 0000000..b8d2fc4 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.playback/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_device_playback) + +add_executable(${PROJECT_NAME} device_playback.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.playback/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.playback/README.md new file mode 100644 index 0000000..d1fae53 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.playback/README.md @@ -0,0 +1,63 @@ +# C++ Sample:2.device.playback + +## Overview + +This example demonstrates how to use the SDK to read and visualize data from a ROS bag file (.bag) with Orbbec camera streams. + +### Knowledge + +**Pipeline**: Manages data streams with multi-channel configuration, frame synchronization, and aggregation capabilities. +**PlaybackDevice**: Reads sensor data from a ROS bag file and feeds it into the processing pipeline. + +## code overview + +1. Initialize Playback Device and Pipeline + + Create a playback device from a ROS bag file and configure the processing pipeline: + + ```cpp + // Create a playback device with a Rosbag file + std::shared_ptr playback = std::make_shared(filePath); + // Create a pipeline with the playback device + std::shared_ptr pipe = std::make_shared(playback); + ``` + +2. Enable Recorded Streams + Activate all sensor streams available in the bag file: + + ```cpp + std::shared_ptr config = std::make_shared(); + auto sensorList = playback->getSensorList(); + for(uint32_t i = 0; i < sensorList->getCount(); i++) { + auto sensorType = sensorList->getSensorType(i); + + config->enableStream(sensorType); + } + ``` + +3. Start the Pipeline with the Config + + ```cpp + pipe->start(config); + ``` + +4. Automatically restart playback when reaching file end: + + ```cpp + playback->setPlaybackStatusChangeCallback([&](OBPlaybackStatus status) { + if(status == OB_PLAYBACK_STOPPED && !exited) { + pipe->stop(); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + pipe->start(config); + } + }); + ``` + +## Run Sample + +Press the 'Esc' key in the window to exit the program. + +### Result + +![image](../../docs/resource/device_playbcak.jpg) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.playback/device_playback.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.playback/device_playback.cpp new file mode 100644 index 0000000..5178750 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.playback/device_playback.cpp @@ -0,0 +1,105 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include +#include +#include + +bool getRosbagPath(std::string &rosbagPath); + +int main(void) try { + std::atomic exited(false); + std::string filePath; + // Get valid .bag file path from user input + getRosbagPath(filePath); + + // Create a playback device with a Rosbag file + std::shared_ptr playback = std::make_shared(filePath); + // Create a pipeline with the playback device + std::shared_ptr pipe = std::make_shared(playback); + // Enable all recording streams from the playback device + std::shared_ptr config = std::make_shared(); + std::cout << "duration: " << playback->getDuration() << std::endl; + + std::mutex frameMutex; + std::shared_ptr renderFrameSet; + auto frameCallback = [&](std::shared_ptr frameSet) { + std::lock_guard lock(frameMutex); + renderFrameSet = frameSet; + }; + + // Set playback status change callback, when the playback stops, start the pipeline again with the same config + playback->setPlaybackStatusChangeCallback([&](OBPlaybackStatus status) { + if(status == OB_PLAYBACK_STOPPED && !exited) { + pipe->stop(); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + std::cout << "Replay again" << std::endl; + pipe->start(config, frameCallback); + } + }); + + auto sensorList = playback->getSensorList(); + for(uint32_t i = 0; i < sensorList->getCount(); i++) { + auto sensorType = sensorList->getSensorType(i); + + config->enableStream(sensorType); + } + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ANY_SITUATION); + + // Start the pipeline with the config + pipe->start(config, frameCallback); + + ob_smpl::CVWindow win("Playback", 1280, 720, ob_smpl::ARRANGE_GRID); + while(win.run() && !exited) { + std::lock_guard lock(frameMutex); + if(renderFrameSet == nullptr) { + continue; + } + win.pushFramesToView(renderFrameSet); + } + exited = true; + + pipe->stop(); + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +bool getRosbagPath(std::string &rosbagPath) { + while(true) { + std::cout << "Please input the path of the Rosbag file (.bag) to playback: \n"; + std::cout << "Path: "; + std::string input; + std::getline(std::cin, input); + + // Remove leading and trailing whitespaces + input.erase(std::find_if(input.rbegin(), input.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), input.end()); + + // Remove leading and trailing quotes + if(!input.empty() && input.front() == '\'' && input.back() == '\'') { + input = input.substr(1, input.size() - 2); + } + + if(!input.empty() && input.front() == '\"' && input.back() == '\"') { + input = input.substr(1, input.size() - 2); + } + + // Validate .bag extension + if(input.size() > 4 && input.substr(input.size() - 4) == ".bag") { + rosbagPath = input; + std::cout << "Playback file confirmed: " << rosbagPath << "\n\n"; + return true; + } + + std::cout << "Invalid file format. Please provide a .bag file.\n\n"; + } +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record.nogui/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record.nogui/CMakeLists.txt new file mode 100644 index 0000000..e0d4a8d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record.nogui/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_device_record_nogui) + +add_executable(${PROJECT_NAME} device_record_nogui.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record.nogui/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record.nogui/README.md new file mode 100644 index 0000000..5767366 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record.nogui/README.md @@ -0,0 +1,50 @@ +# C++ Sample:2.device.record.nogui + +## Overview + +This example demonstrates how to use the SDK to record video/sensor stream data from an Orbbec camera and output a ROS bag file (.bag). +It is a command-line (CLI) tool that records streams directly without rendering video frames. + +### Knowledge + +- **Pipeline**: Manages data streams with capabilities for multi-channel configuration, stream switching, frame aggregation, and synchronization. +- **RecordDevice**: Handles data recording to a ROS bag file, supporting simultaneous capture from multiple sensors and streams. + +## code overview + +1. Create a Context object and get the specified device. + + ```cpp + std::shared_ptr context = std::make_shared(); + + auto device = deviceList->getDevice(0); + ``` + +2. Instantiate a RecordDevice to capture all streams from the connected device into a ROS bag file: + + ```cpp + auto recordDevice = std::make_shared(device, filePath); + ``` + +3. Configure and start the pipeline with a frame callback for real-time preview: + + ```cpp + pipe->start(config, [&](std::shared_ptr frameSet) { + std::lock_guard lock(frameMutex); + // Do something for frameset + }); + ``` +4. Destroy the RecordDevice to flush and save the ROS bag file: + + ```cpp + recordDevice = nullptr; + ``` + +## Run Sample + +Press the 'Esc' key in the window to exit the program. + +### Result + +![image](../../docs/resource/device_record_nogui.jpg) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record.nogui/device_record_nogui.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record.nogui/device_record_nogui.cpp new file mode 100644 index 0000000..2680e5d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record.nogui/device_record_nogui.cpp @@ -0,0 +1,150 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include +#include +#include +#include +#include +#include + +#define IS_ASTRA_MINI_DEVICE(pid) (pid == 0x069d || pid == 0x065b || pid == 0x065e) + +int main(void) try { + std::cout << "Please enter the output filename (with .bag extension) and press Enter to start recording: "; + std::string filePath; + std::getline(std::cin, filePath); + + // Create a context, for getting devices and sensors + std::shared_ptr context = std::make_shared(); + + // Query device list + auto deviceList = context->queryDeviceList(); + if(deviceList->getCount() < 1) { + std::cout << "No device found! Please connect a supported device and retry this program." << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); + } + + // Acquire first available device + auto device = deviceList->getDevice(0); + + // Create a pipeline the specified device + auto pipe = std::make_shared(device); + + // Activate device clock synchronization + try { + device->timerSyncWithHost(); + } + catch(ob::Error &e) { + std::cerr << "Function: " << e.getFunction() << "\nArgs: " << e.getArgs() << "\nMessage: " << e.what() << "\nException Type: " << e.getExceptionType() + << std::endl; + } + + // Create a config and enable all streams + std::shared_ptr config = std::make_shared(); + auto sensorList = device->getSensorList(); + auto count = sensorList->getCount(); + for(uint32_t i = 0; i < count; i++) { + auto sensor = sensorList->getSensor(i); + auto sensorType = sensor->getType(); + auto profileList = sensor->getStreamProfileList(); // Get profileList to create Sensor object in advance + if(IS_ASTRA_MINI_DEVICE(device->getDeviceInfo()->getPid())) { + if(sensorType == OB_SENSOR_IR) { + continue; + } + } + config->enableStream(sensorType); + } + + std::mutex frameMutex; + std::map frameCountMap; + pipe->start(config, [&](std::shared_ptr frameSet) { + if(frameSet == nullptr) { + return; + } + + std::lock_guard lock(frameMutex); + auto count = frameSet->getCount(); + for(uint32_t i = 0; i < count; i++) { + auto frame = frameSet->getFrameByIndex(i); + if(frame) { + auto type = frame->getType(); + frameCountMap[type]++; + } + } + }); + + // Initialize recording device with output file + auto startTime = ob_smpl::getNowTimesMs(); + uint32_t waitTime = 1000; + auto recordDevice = std::make_shared(device, filePath); + + // operation prompt + std::cout << "Streams and recorder have started!" << std::endl; + std::cout << "Press ESC, 'q', or 'Q' to stop recording and exit safely." << std::endl; + std::cout << "IMPORTANT: Always use ESC/q/Q to stop! Otherwise, the bag file will be corrupted and unplayable." << std::endl << std::endl; + + do { + auto key = ob_smpl::waitForKeyPressed(waitTime); + if(key == ESC_KEY || key == 'q' || key == 'Q') { + break; + } + auto currentTime = ob_smpl::getNowTimesMs(); + if(currentTime > startTime + waitTime) { + std::map tempCountMap; + uint64_t duration; + { + // Copy data + std::lock_guard lock(frameMutex); + + // get time again + currentTime = ob_smpl::getNowTimesMs(); + duration = currentTime - startTime; + if(!frameCountMap.empty()) { + startTime = currentTime; + waitTime = 2000; // Change to 2s for next time + tempCountMap = frameCountMap; + for(auto &item: frameCountMap) { + item.second = 0; // reset count + } + } + } + + std::string seperate = ""; + if(tempCountMap.empty()) { + std::cout << "Recording... Current FPS: 0" << std::endl; + } + else { + std::cout << "Recording... Current FPS: "; + for(const auto &item: tempCountMap) { + auto name = ob::TypeHelper::convertOBFrameTypeToString(item.first); + float rate = item.second / (duration / 1000.0f); + + std::cout << std::fixed << std::setprecision(2) << std::showpoint; + std::cout << seperate << name << "=" << rate; + seperate = ", "; + } + std::cout << std::endl; + } + } + } while(true); + + // stop the pipeline + pipe->stop(); + + // Flush and save recording file + recordDevice = nullptr; + return 0; +} +catch(ob::Error &e) { + std::cerr << "Function: " << e.getFunction() << "\nArgs: " << e.getArgs() << "\nMessage: " << e.what() << "\nException Type: " << e.getExceptionType() + << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record/CMakeLists.txt new file mode 100644 index 0000000..d221645 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_device_record) + +add_executable(${PROJECT_NAME} device_record.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record/README.md new file mode 100644 index 0000000..552d6a7 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record/README.md @@ -0,0 +1,49 @@ +# C++ Sample:2.device.record + +## Overview + +This example demonstrates how to use the SDK to record video/sensor stream data from an Orbbec camera and output a ROS bag file (.bag). + +### Knowledge + +- **Pipeline**: Manages data streams with capabilities for multi-channel configuration, stream switching, frame aggregation, and synchronization. +- **RecordDevice**: Handles data recording to a ROS bag file, supporting simultaneous capture from multiple sensors and streams. + +## code overview + +1. Create a Context object and get the specified device. + + ```cpp + std::shared_ptr context = std::make_shared(); + + auto device = deviceList->getDevice(0); + ``` + +2. Instantiate a RecordDevice to capture all streams from the connected device into a ROS bag file: + + ```cpp + auto recordDevice = std::make_shared(device, filePath); + ``` + +3. Configure and start the pipeline with a frame callback for real-time preview: + + ```cpp + pipe->start(config, [&](std::shared_ptr frameSet) { + std::lock_guard lock(frameMutex); + renderFrameSet = frameSet; + }); + ``` +4. Destroy the RecordDevice to flush and save the ROS bag file: + + ```cpp + recordDevice = nullptr; + ``` + +## Run Sample + +Press the 'Esc' key in the window to exit the program. + +### Result + +![image](../../docs/resource/device_record.jpg) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record/device_record.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record/device_record.cpp new file mode 100644 index 0000000..59a25bf --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/2.device.record/device_record.cpp @@ -0,0 +1,119 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include +#include +#include + +#define IS_ASTRA_MINI_DEVICE(pid) (pid == 0x069d || pid == 0x065b || pid == 0x065e) + +std::atomic isPaused{false}; + +void handleKeyPress(ob_smpl::CVWindow &win, std::shared_ptr recorder, int key); + +int main(void) try { + std::cout << "Please enter the output filename (with .bag extension) and press Enter to start recording: "; + std::string filePath; + std::getline(std::cin, filePath); + + // Create a context, for getting devices and sensors + std::shared_ptr context = std::make_shared(); + + // Query device list + auto deviceList = context->queryDeviceList(); + if(deviceList->getCount() < 1) { + std::cout << "No device found! Please connect a supported device and retry this program." << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); + } + + // Acquire first available device + auto device = deviceList->getDevice(0); + + // Create a pipeline the specified device + auto pipe = std::make_shared(device); + + // Activate device clock synchronization + try { + device->timerSyncWithHost(); + } + catch(ob::Error &e) { + std::cerr << "Function: " << e.getFunction() << "\nArgs: " << e.getArgs() << "\nMessage: " << e.what() << "\nException Type: " << e.getExceptionType() + << std::endl; + } + + // Create a config and enable all streams + std::shared_ptr config = std::make_shared(); + auto sensorList = device->getSensorList(); + for(uint32_t i = 0; i < sensorList->getCount(); i++) { + auto sensorType = sensorList->getSensorType(i); + if(IS_ASTRA_MINI_DEVICE(device->getDeviceInfo()->getPid())) { + if(sensorType == OB_SENSOR_IR) { + continue; + } + } + config->enableStream(sensorType); + } + + std::mutex frameMutex; + std::shared_ptr renderFrameSet; + pipe->start(config, [&](std::shared_ptr frameSet) { + std::lock_guard lock(frameMutex); + renderFrameSet = frameSet; + }); + + // Initialize recording device with output file + auto recordDevice = std::make_shared(device, filePath); + std::cout << "Streams and recorder have started!" << std::endl; + std::cout << "Press ESC to stop recording and exit safely." << std::endl; + std::cout << "IMPORTANT: Always use ESC to stop! Otherwise, the bag file will be corrupted and unplayable." << std::endl << std::endl; + + ob_smpl::CVWindow win("Record", 1280, 720, ob_smpl::ARRANGE_GRID); + + win.setKeyPrompt("Press 'S' to pause/resume recording."); + // set the callback function for the window to handle key press events + win.setKeyPressedCallback([&win, recordDevice](int key) { handleKeyPress(win, recordDevice, key); }); + + while(win.run()) { + std::lock_guard lock(frameMutex); + if(renderFrameSet == nullptr) { + continue; + } + win.pushFramesToView(renderFrameSet); + } + + pipe->stop(); + + // Flush and save recording file + recordDevice = nullptr; + + return 0; +} +catch(ob::Error &e) { + std::cerr << "Function: " << e.getFunction() << "\nArgs: " << e.getArgs() << "\nMessage: " << e.what() << "\nException Type: " << e.getExceptionType() + << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +void handleKeyPress(ob_smpl::CVWindow& win, std::shared_ptr recorder, int key) { + if(key == 'S' || key == 's') { + if(!isPaused) { + recorder->pause(); + isPaused.store(true); + win.addLog("[PAUSED] Recording paused"); + } + else { + recorder->resume(); + isPaused.store(false); + win.addLog("[RESUMED] Recording resumed"); + } + } +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.common_usages/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.common_usages/CMakeLists.txt new file mode 100644 index 0000000..7d6789e --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.common_usages/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_common_usages) + +add_executable(${PROJECT_NAME} common_usages.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.common_usages/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.common_usages/README.md new file mode 100644 index 0000000..69374ab --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.common_usages/README.md @@ -0,0 +1,77 @@ +# C++ Sample:3.advanced.common_usages + +## Overview + +Use the SDK interface to view camera related information, set related parameters, and display the video stream + +### Knowledge + +Context is the environment context, the first object created during initialization, which can be used to perform some settings, including but not limited to device status change callbacks, log level settings, etc. Context can access multiple Devices. + +Device is the device object, which can be used to obtain the device information, such as the model, serial number, and various sensors.One actual hardware device corresponds to one Device object. + +## code overview + +1. Register device callback + + ```cpp + // Create ob:Context. + ctx = std::make_shared(); + ctx.setDeviceChangedCallback( []( std::shared_ptr< ob::DeviceList > removedList, std::shared_ptr< ob::DeviceList > addedList ) { + DeviceDisconnectCallback( removedList ); + DeviceConnectCallback( addedList ); + } ); + ``` + +2. Get the device list and print out the information, then use pipeline to start the video stream. + + ```cpp + // Query the list of connected devices. + std::shared_ptr devices = ctx->queryDeviceList(); + + // Handle connected devices(and open one device) + handleDeviceConnected(devices); + ``` + +3. Block thread waiting for device connection + + ```cpp + while(!device) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + ``` + +4. Execute corresponding settings according to the commands entered by the user. The following is an introduction to some setting functions + + ```cpp + //Get the basic parameters of the camera, including connection type, device model, etc. + void getDeviceInformation() + //Get camera sensor intrinsics, distortion and pixels + void getCameraParams() + //Laser switch function + void switchLaser() + //Laser safety protection and ranging function switch + void switchLDP() + //Get the laser safety protection and ranging function status + void getLDPStatus() + //Color auto-exposure switch + void switchColorAE() + //Color exposure value adjustment + void setColorExposureValue(bool increase) + //Color gain value adjustment + void setColorGainValue(bool increase) + //Depth auto-exposure switch + void setDepthExposureValue(bool increase) + //Depth exposure value adjustment + void setDepthGainValue(bool increase) + ‵‵‵ + +## Run Sample + +Press the button according to the interface prompts + +### Result + +![image](../../docs/resource/common1.jpg) + +![image](../../docs/resource/common2.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.common_usages/common_usages.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.common_usages/common_usages.cpp new file mode 100644 index 0000000..188e1ea --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.common_usages/common_usages.cpp @@ -0,0 +1,921 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include +#include +#include + +const std::map gemini_330_list = { { "Gemini 335", 0x0800 }, { "Gemini 335L", 0x0804 }, { "Gemini 336", 0x0803 }, { "Gemini 336L", 0x0807 }, + { "Gemini 330", 0x0801 }, { "Gemini 330L", 0x0805 }, { "DabaiA", 0x0A12 }, { "DabaiAL", 0x0A13 }, + { "Gemini 345", 0x0812 }, { "Gemini 345Lg", 0x0813 }, { "CAM-5330", 0x0816 }, { "CAM-5530", 0x0817 },{"Gemini 338",0x0818} }; + +const std::map sensorTypeToStringMap = { { OB_SENSOR_COLOR, "Color profile: " }, + { OB_SENSOR_DEPTH, "Depth profile: " }, + { OB_SENSOR_IR, "IR profile: " }, + { OB_SENSOR_IR_LEFT, "Left IR profile: " }, + { OB_SENSOR_IR_RIGHT, "Right IR profile: " } }; + +bool isGemini330Series(int pid) { + bool find = false; + for(auto it = gemini_330_list.begin(); it != gemini_330_list.end(); ++it) { + if(it->second == pid) { + find = true; + break; + } + } + return find; +} + +std::shared_ptr win = nullptr; +std::shared_ptr ctx = nullptr; + +std::shared_ptr device = nullptr; +std::shared_ptr pipeline = nullptr; +std::recursive_mutex deviceMutex; + +bool irRightMirrorSupport = false; + +std::map> profilesMap; +std::shared_ptr depthProfile = nullptr; +std::shared_ptr irProfile = nullptr; + +std::shared_ptr align = nullptr; + +void handleDeviceConnected(std::shared_ptr connectList); +void handleDeviceDisconnected(std::shared_ptr disconnectList); +void switchDepthWorkMode(); +void turnOffHwD2d(); +void setDepthUnit(); +void setDepthSoftFilter(); + +void printUsage(); +void commandProcess(std::string cmd); + +void handleFrameset(std::shared_ptr frameset); +void startStream(); + +int main(void) try { + + // create window for render + win = std::make_shared("CommonUsages", 1280, 720, ob_smpl::ARRANGE_GRID); + + // Set log severity. disable log, please set OB_LOG_SEVERITY_OFF. + ob::Context::setLoggerSeverity(OB_LOG_SEVERITY_ERROR); + + // Create ob:Context. + ctx = std::make_shared(); + + // create align filter + align = ob::FilterFactory::createFilter("Align"); + + // Register device callback + ctx->setDeviceChangedCallback([](std::shared_ptr removedList, std::shared_ptr addedList) { + handleDeviceDisconnected(removedList); + handleDeviceConnected(addedList); + }); + + // Query the list of connected devices. + std::shared_ptr devices = ctx->queryDeviceList(); + + // Handle connected devices(and open one device) + handleDeviceConnected(devices); + + if(!device) { + std::cout << "Waiting for connect device..."; + while(!device) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + irRightMirrorSupport = device->isPropertySupported(OB_PROP_IR_RIGHT_MIRROR_BOOL, OB_PERMISSION_READ_WRITE); + printUsage(); + + auto inputWatchThread = std::thread([]{ + while(true) { + std::string cmd; + std::cout << "\nInput command: "; + std::getline(std::cin, cmd); + if(cmd == "quit" || cmd == "q") { + win->close(); + break; + } + else { + commandProcess(cmd); + } + } + }); + inputWatchThread.detach(); + + while(win->run()) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + if(pipeline) { + pipeline->stop(); + } + + // destruct all global variables here before exiting main + irProfile.reset(); + depthProfile.reset(); + profilesMap.clear(); + pipeline.reset(); + device.reset(); + devices.reset(); + align.reset(); + ctx.reset(); + win.reset(); + + return 0; +} + +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +// Device connection callback +void handleDeviceConnected(std::shared_ptr devices) { + // Get the number of connected devices + if(devices->getCount() == 0) { + return; + } + + const auto deviceCount = devices->getCount(); + for(uint32_t i = 0; i < deviceCount; i++) { + std::string deviceSN = devices->getSerialNumber(i); + std::cout << "Found device connected, SN: " << deviceSN << std::endl; + } + + std::unique_lock lk(deviceMutex); + if(!device) { + // open default device (device index=0) + device = devices->getDevice(0); + pipeline = std::make_shared(device); + std::cout << "Open device success, SN: " << devices->getSerialNumber(0) << std::endl; + + startStream(); + } +} + +// Device disconnect callback +void handleDeviceDisconnected(std::shared_ptr disconnectList) { + std::string currentDevSn = ""; + { + std::unique_lock lk(deviceMutex); + if(device) { + std::shared_ptr devInfo = device->getDeviceInfo(); + currentDevSn = devInfo->getSerialNumber(); + } + } + const auto deviceCount = disconnectList->getCount(); + for(uint32_t i = 0; i < deviceCount; i++) { + std::string deviceSN = disconnectList->getSerialNumber(i); + std::cout << "Device disconnected, SN: " << deviceSN << std::endl; + if(currentDevSn == deviceSN) { + device.reset(); // release device + pipeline.reset(); // release pipeline + std::cout << "Current device disconnected" << std::endl; + } + } +} + +void switchDepthWorkMode() { + std::unique_lock lk(deviceMutex); + // Check whether the camera depth working mode is supported + if(!device->isPropertySupported(OB_STRUCT_CURRENT_DEPTH_ALG_MODE, OB_PERMISSION_READ_WRITE)) { + return; + } + + // Query the current camera depth mode + auto curDepthMode = device->getCurrentDepthWorkMode(); + std::cout << "current depth work mode: " << curDepthMode.name << std::endl; + + // Get the list of camera depth modes + auto depthModeList = device->getDepthWorkModeList(); + std::cout << "depthModeList size: " << depthModeList->getCount() << std::endl; + for(uint32_t i = 0; i < depthModeList->getCount(); i++) { + std::cout << "depthModeList[" << i << "]: " << (*depthModeList)[i].name << std::endl; + } + + // switch depth work mode to default (index=0) mode, user can switch to ohter mode like this. + device->switchDepthWorkMode((*depthModeList)[0].name); + std::cout << "switch depth work mode to:" << (*depthModeList)[0].name << std::endl; + + // It is require to reopen the device and pipeline after switch depth work mode + auto deviceInfo = device->getDeviceInfo(); + device.reset(); + pipeline.reset(); + auto deviceList = ctx->queryDeviceList(); + device = deviceList->getDeviceBySN(deviceInfo->getSerialNumber()); // using serial number to create device + pipeline = std::make_shared(device); +} + +void turnOffHwD2d() { + try { + // Some models dose not support this feature + if(device->isPropertySupported(OB_PROP_DISPARITY_TO_DEPTH_BOOL, OB_PERMISSION_WRITE)) { + device->setBoolProperty(OB_PROP_DISPARITY_TO_DEPTH_BOOL, false); + std::cout << "turn off hardware disparity to depth converter (Turn on Software D2D)" << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + exit(EXIT_FAILURE); + } +} + +void setDepthUnit() { + try { + if(device->isPropertySupported(OB_PROP_DEPTH_PRECISION_LEVEL_INT, OB_PERMISSION_WRITE)) { + device->setIntProperty(OB_PROP_DEPTH_PRECISION_LEVEL_INT, OB_PRECISION_1MM); + std::cout << "set depth unit to 1mm" << std::endl; + } + else { + std::cerr << "Depth precision level switch is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + exit(EXIT_FAILURE); + } +} + +void setDepthSoftFilter() { + try { + if(device->isPropertySupported(OB_PROP_DEPTH_NOISE_REMOVAL_FILTER_BOOL, OB_PERMISSION_WRITE)) { + device->setBoolProperty(OB_PROP_DEPTH_NOISE_REMOVAL_FILTER_BOOL, true); + std::cout << "turn on depth soft filter" << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + exit(EXIT_FAILURE); + } +} + +void startStream() { + std::unique_lock lk(deviceMutex); + + device = pipeline->getDevice(); + auto sensorList = device->getSensorList(); + + // Configure which streams to enable or disable for the Pipeline by creating a Config. + std::shared_ptr config = std::make_shared(); + + for(uint32_t index = 0; index < sensorList->getCount(); index++) { + // Query all supported infrared sensor type and enable the infrared stream. + // For dual infrared device, enable the left and right infrared streams. + // For single infrared device, enable the infrared stream. + OBSensorType sensorType = sensorList->getSensorType(index); + if(sensorType == OB_SENSOR_IR || sensorType == OB_SENSOR_IR_LEFT || sensorType == OB_SENSOR_IR_RIGHT || sensorType == OB_SENSOR_COLOR + || sensorType == OB_SENSOR_DEPTH) { + try { + auto sensor = sensorList->getSensor(sensorType); + auto profileList = sensor->getStreamProfileList(); + if(profileList->getCount() > 0) { + // get default (index=0) stream profile + auto defProfile = profileList->getProfile(OB_PROFILE_DEFAULT); + + auto defVsProfile = defProfile->as(); + profilesMap.insert(std::make_pair(sensorType, defVsProfile)); + auto it = sensorTypeToStringMap.find(sensorType); + if(it != sensorTypeToStringMap.end()) { + std::cout << it->second << defVsProfile->getWidth() << "x" << defVsProfile->getHeight() << " @ " << defVsProfile->getFps() << "fps" + << std::endl; + } + else { + std::cout << "unknown profile: " << defVsProfile->getWidth() << "x" << defVsProfile->getHeight() << " @ " << defVsProfile->getFps() + << "fps" << std::endl; + } + + // enable color stream. + config->enableStream(defVsProfile); + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } + } + + // start pipeline + pipeline->start(config, handleFrameset); + std::cout << "Stream started!" << std::endl; +} +std::shared_ptr fileterAlign(std::shared_ptr frameset) { + auto newFrame = align->process(frameset); + if(!newFrame) { + return nullptr; + } + auto newFrameSet = newFrame->as(); + return newFrameSet; +} +void handleFrameset(std::shared_ptr frameset) { + auto alignFrameSet = fileterAlign(frameset); + // If no depthframe is present, it is discarded + if(frameset->getCount() < 3) { + return; + } + win->pushFramesToView(alignFrameSet); +} + +void getDeviceInformation() { + std::unique_lock lk(deviceMutex); + if(device) { + auto info = device->getDeviceInfo(); + // Get the name of the device + std::cout << "-Device name: " << info->getName() << std::endl; + // Get the pid, vid, uid of the device + std::cout << "-Device pid: 0x" << std::hex << std::setw(4) << std::setfill('0') << info->getPid() << " vid: 0x" << std::hex << std::setw(4) + << std::setfill('0') << info->getVid() << " uid: " << info->getUid() << std::dec << std::endl; + // By getting the firmware version number of the device + auto fwVer = info->getFirmwareVersion(); + std::cout << "-Firmware version: " << fwVer << std::endl; + // By getting the serial number of the device + auto sn = info->getSerialNumber(); + std::cout << "-Serial number: " << sn << std::endl; + // By getting the connection type of the device + auto connectType = info->getConnectionType(); + std::cout << "-ConnectionType: " << connectType << std::endl; + } +} + +void getCameraParams() { + std::unique_lock lk(deviceMutex); + if(pipeline) { + try { + for(const auto &item: profilesMap) { + auto profile = item.second; + auto type = item.first; + auto intrinsics = profile->getIntrinsic(); + auto distortion = profile->getDistortion(); + auto typeString = ob::TypeHelper::convertOBSensorTypeToString(type); + std::cout << typeString << " intrinsics: " + << "fx:" << intrinsics.fx << ", fy: " << intrinsics.fy << ", cx: " << intrinsics.cx << ", cy: " << intrinsics.cy + << " ,width: " << intrinsics.width << ", height: " << intrinsics.height << std::endl; + + std::cout << typeString << " distortion: " + << "k1:" << distortion.k1 << ", k2:" << distortion.k2 << ", k3:" << distortion.k3 << ", k4:" << distortion.k4 + << ", k5:" << distortion.k5 << ", k6:" << distortion.k6 << ", p1:" << distortion.p1 << ", p2:" << distortion.p2 << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void switchLaser() { + std::unique_lock lk(deviceMutex); + if(device) { + try { + auto pid = device->getDeviceInfo()->getPid(); + OBPropertyID propertyId = OB_PROP_LASER_BOOL; + if(isGemini330Series(pid)) { + propertyId = OB_PROP_LASER_CONTROL_INT; + } + + if(device->isPropertySupported(propertyId, OB_PERMISSION_READ)) { + bool value = device->getBoolProperty(propertyId); + if(device->isPropertySupported(propertyId, OB_PERMISSION_WRITE)) { + device->setBoolProperty(propertyId, !value); + if(!value) { + std::cout << "laser turn on!" << std::endl; + } + else { + std::cout << "laser turn off!" << std::endl; + } + } + } + else { + std::cerr << "Laser switch property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void switchLDP() { + std::unique_lock lk(deviceMutex); + if(device) { + try { + if(device->isPropertySupported(OB_PROP_LDP_BOOL, OB_PERMISSION_READ)) { + bool value = device->getBoolProperty(OB_PROP_LDP_BOOL); + if(device->isPropertySupported(OB_PROP_LDP_BOOL, OB_PERMISSION_WRITE)) { + device->setBoolProperty(OB_PROP_LDP_BOOL, !value); + if(!value) { + std::cout << "LDP turn on!" << std::endl; + } + else { + std::cout << "LDP turn off!" << std::endl; + } + std::cout << "Attention: For some models, it is require to restart depth stream after turn on/of LDP. Input \"stream\" command " + "to restart stream!" + << std::endl; + } + } + else { + std::cerr << "LDP switch property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void getLDPStatus() { + std::unique_lock lk(deviceMutex); + if(device) { + try { + if(device->isPropertySupported(OB_PROP_LDP_STATUS_BOOL, OB_PERMISSION_READ)) { + bool value = device->getBoolProperty(OB_PROP_LDP_STATUS_BOOL); + std::cout << "LDP status:" << value << std::endl; + } + else { + std::cerr << "LDP status property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void switchDepthAE() { + std::unique_lock lk(deviceMutex); + if(device) { + try { + if(device->isPropertySupported(OB_PROP_DEPTH_AUTO_EXPOSURE_BOOL, OB_PERMISSION_READ)) { + bool value = device->getBoolProperty(OB_PROP_DEPTH_AUTO_EXPOSURE_BOOL); + if(device->isPropertySupported(OB_PROP_DEPTH_AUTO_EXPOSURE_BOOL, OB_PERMISSION_WRITE)) { + device->setBoolProperty(OB_PROP_DEPTH_AUTO_EXPOSURE_BOOL, !value); + if(!value) { + std::cout << "Depth Auto-Exposure on!" << std::endl; + } + else { + std::cout << "Depth Auto-Exposure off!" << std::endl; + } + } + } + else { + std::cerr << "Depth Auto-Exposure switch property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} +void switchColorAE() { + std::unique_lock lk(deviceMutex); + if(device) { + try { + if(device->isPropertySupported(OB_PROP_COLOR_AUTO_EXPOSURE_BOOL, OB_PERMISSION_READ)) { + bool value = device->getBoolProperty(OB_PROP_COLOR_AUTO_EXPOSURE_BOOL); + if(device->isPropertySupported(OB_PROP_COLOR_AUTO_EXPOSURE_BOOL, OB_PERMISSION_WRITE)) { + device->setBoolProperty(OB_PROP_COLOR_AUTO_EXPOSURE_BOOL, !value); + if(!value) { + std::cout << "Color Auto-Exposure on!" << std::endl; + } + else { + std::cout << "Color Auto-Exposure off!" << std::endl; + } + } + } + else { + std::cerr << "Color Auto-Exposure switch property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void switchDepthMirror() { + std::unique_lock lk(deviceMutex); + if(device) { + try { + if(device->isPropertySupported(OB_PROP_DEPTH_MIRROR_BOOL, OB_PERMISSION_READ)) { + bool value = device->getBoolProperty(OB_PROP_DEPTH_MIRROR_BOOL); + if(device->isPropertySupported(OB_PROP_DEPTH_MIRROR_BOOL, OB_PERMISSION_WRITE)) { + device->setBoolProperty(OB_PROP_DEPTH_MIRROR_BOOL, !value); + if(!value) { + std::cout << "Note: Currently with the D2C(SW) turned on, Depth Mirror will not work!" << std::endl; + std::cout << "Depth mirror on!" << std::endl; + } + else { + std::cout << "Depth mirror off!" << std::endl; + } + } + } + else { + std::cerr << "Depth mirror switch property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void switchIRMirror() { + std::unique_lock lk(deviceMutex); + if(device) { + try { + if(device->isPropertySupported(OB_PROP_IR_MIRROR_BOOL, OB_PERMISSION_READ)) { + bool value = device->getBoolProperty(OB_PROP_IR_MIRROR_BOOL); + if(device->isPropertySupported(OB_PROP_IR_MIRROR_BOOL, OB_PERMISSION_WRITE)) { + device->setBoolProperty(OB_PROP_IR_MIRROR_BOOL, !value); + if(!value) { + std::cout << "IR mirror on!" << std::endl; + } + else { + std::cout << "IR mirror off!" << std::endl; + } + } + } + else { + std::cerr << "IR mirror switch property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void switchIRRightMirror() { + std::unique_lock lk(deviceMutex); + if(device) { + try { + if(device->isPropertySupported(OB_PROP_IR_RIGHT_MIRROR_BOOL, OB_PERMISSION_READ)) { + bool value = device->getBoolProperty(OB_PROP_IR_RIGHT_MIRROR_BOOL); + if(device->isPropertySupported(OB_PROP_IR_RIGHT_MIRROR_BOOL, OB_PERMISSION_WRITE)) { + device->setBoolProperty(OB_PROP_IR_RIGHT_MIRROR_BOOL, !value); + if(!value) { + std::cout << "IR Right mirror on!" << std::endl; + } + else { + std::cout << "IR Right mirror off!" << std::endl; + } + } + } + else { + std::cerr << "IR mirror switch property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void switchColorMirror() { + std::unique_lock lk(deviceMutex); + if(device) { + try { + if(device->isPropertySupported(OB_PROP_COLOR_MIRROR_BOOL, OB_PERMISSION_READ)) { + bool value = device->getBoolProperty(OB_PROP_COLOR_MIRROR_BOOL); + if(device->isPropertySupported(OB_PROP_COLOR_MIRROR_BOOL, OB_PERMISSION_WRITE)) { + device->setBoolProperty(OB_PROP_COLOR_MIRROR_BOOL, !value); + if(!value) { + std::cout << "Color mirror on!" << std::endl; + } + else { + std::cout << "Color mirror off!" << std::endl; + } + } + } + else { + std::cerr << "Color mirror switch property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void setDepthExposureValue(bool increase) { + if(device) { + try { + if(device->isPropertySupported(OB_PROP_DEPTH_AUTO_EXPOSURE_BOOL, OB_PERMISSION_READ_WRITE)) { + bool value = device->getBoolProperty(OB_PROP_DEPTH_AUTO_EXPOSURE_BOOL); + if(value) { + device->setBoolProperty(OB_PROP_DEPTH_AUTO_EXPOSURE_BOOL, false); + std::cout << "Depth AE close." << std::endl; + } + } + if(device->isPropertySupported(OB_PROP_DEPTH_EXPOSURE_INT, OB_PERMISSION_READ)) { + // get the value range + OBIntPropertyRange valueRange = device->getIntPropertyRange(OB_PROP_DEPTH_EXPOSURE_INT); + std::cout << "Depth current exposure max:" << valueRange.max << ", min:" << valueRange.min << std::endl; + + int value = device->getIntProperty(OB_PROP_DEPTH_EXPOSURE_INT); + std::cout << "Depth current exposure:" << value << std::endl; + if(device->isPropertySupported(OB_PROP_DEPTH_EXPOSURE_INT, OB_PERMISSION_WRITE)) { + if(increase) { + value += (valueRange.max - valueRange.min) / 10; + if(value > valueRange.max) { + value = valueRange.max; + } + } + else { + value -= (valueRange.max - valueRange.min) / 10; + if(value < valueRange.min) { + value = valueRange.min; + } + } + + // Ensure that the value meet the step value requirements + value = valueRange.min + (value - valueRange.min) / valueRange.step * valueRange.step; + + std::cout << "Set depth exposure:" << value << std::endl; + device->setIntProperty(OB_PROP_DEPTH_EXPOSURE_INT, value); + } + else { + std::cerr << "Depth exposure set property is not supported." << std::endl; + } + } + else { + std::cerr << "Depth exposure get property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void setColorExposureValue(bool increase) { + if(device) { + try { + if(device->isPropertySupported(OB_PROP_COLOR_AUTO_EXPOSURE_BOOL, OB_PERMISSION_READ_WRITE)) { + bool value = device->getBoolProperty(OB_PROP_COLOR_AUTO_EXPOSURE_BOOL); + if(value) { + device->setBoolProperty(OB_PROP_COLOR_AUTO_EXPOSURE_BOOL, false); + std::cout << "Color AE close." << std::endl; + } + } + + if(device->isPropertySupported(OB_PROP_COLOR_EXPOSURE_INT, OB_PERMISSION_READ)) { + // get the value range + OBIntPropertyRange valueRange = device->getIntPropertyRange(OB_PROP_COLOR_EXPOSURE_INT); + std::cout << "Color current exposure max:" << valueRange.max << ", min:" << valueRange.min << std::endl; + + int value = device->getIntProperty(OB_PROP_COLOR_EXPOSURE_INT); + std::cout << "Color current exposure:" << value << std::endl; + + if(device->isPropertySupported(OB_PROP_COLOR_EXPOSURE_INT, OB_PERMISSION_WRITE)) { + if(increase) { + value += (valueRange.max - valueRange.min) / 10; + if(value > valueRange.max) { + value = valueRange.max; + } + } + else { + value -= (valueRange.max - valueRange.min) / 10; + if(value < valueRange.min) { + value = valueRange.min; + } + } + + // Ensure that the value meet the step value requirements + value = valueRange.min + (value - valueRange.min) / valueRange.step * valueRange.step; + + std::cout << "Set color exposure:" << value << std::endl; + device->setIntProperty(OB_PROP_COLOR_EXPOSURE_INT, value); + } + else { + std::cerr << "Color exposure set property is not supported." << std::endl; + } + } + else { + std::cerr << "Color exposure get property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void setDepthGainValue(bool increase) { + if(device) { + try { + if(device->isPropertySupported(OB_PROP_DEPTH_GAIN_INT, OB_PERMISSION_READ)) { + OBIntPropertyRange valueRange = device->getIntPropertyRange(OB_PROP_DEPTH_GAIN_INT); + std::cout << "Depth current gain max:" << valueRange.max << ", min:" << valueRange.min << std::endl; + int value = device->getIntProperty(OB_PROP_DEPTH_GAIN_INT); + std::cout << "Depth current gain:" << value << std::endl; + if(device->isPropertySupported(OB_PROP_DEPTH_GAIN_INT, OB_PERMISSION_WRITE)) { + if(increase) { + value += (valueRange.max - valueRange.min) / 10; + if(value > valueRange.max) { + value = valueRange.max; + } + } + else { + value -= (valueRange.max - valueRange.min) / 10; + if(value < valueRange.min) { + value = valueRange.min; + } + } + // Ensure that the value meet the step value requirements + value = valueRange.min + (value - valueRange.min) / valueRange.step * valueRange.step; + + std::cout << "Set depth gain:" << value << std::endl; + device->setIntProperty(OB_PROP_DEPTH_GAIN_INT, value); + } + else { + std::cerr << "Depth gain set property is not supported." << std::endl; + } + } + else { + std::cerr << "Depth gain get property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void setColorGainValue(bool increase) { + if(device) { + try { + if(device->isPropertySupported(OB_PROP_COLOR_GAIN_INT, OB_PERMISSION_READ)) { + OBIntPropertyRange valueRange = device->getIntPropertyRange(OB_PROP_COLOR_GAIN_INT); + std::cout << "Color current gain max:" << valueRange.max << ", min:" << valueRange.min << std::endl; + int value = device->getIntProperty(OB_PROP_COLOR_GAIN_INT); + std::cout << "Color current gain:" << value << std::endl; + if(device->isPropertySupported(OB_PROP_COLOR_GAIN_INT, OB_PERMISSION_WRITE)) { + if(increase) { + value += (valueRange.max - valueRange.min) / 10; + if(value > valueRange.max) { + value = valueRange.max; + } + } + else { + value -= (valueRange.max - valueRange.min) / 10; + if(value < valueRange.min) { + value = valueRange.min; + } + } + + // Ensure that the value meet the step value requirements + value = valueRange.min + (value - valueRange.min) / valueRange.step * valueRange.step; + + std::cout << "Set color gain:" << value << std::endl; + device->setIntProperty(OB_PROP_COLOR_GAIN_INT, value); + } + else { + std::cerr << "Color gain set property is not supported." << std::endl; + } + } + else { + std::cerr << "Color gain get property is not supported." << std::endl; + } + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() + << std::endl; + exit(EXIT_FAILURE); + } + } +} + +void printUsage() { + std::cout << "Support commands:" << std::endl; + std::cout << " info / i - get device information" << std::endl; + std::cout << " param / p - get camera parameter" << std::endl; + std::cout << " laser / l - on/off laser" << std::endl; + std::cout << " ldp / d - on/off LDP" << std::endl; + std::cout << " ldp status / ds - get LDP status" << std::endl; + std::cout << " color ae / ca - on/off Color auto exposure" << std::endl; + std::cout << " inc color value / cei - increase Color exposure value" << std::endl; + std::cout << " dec color value / ced - decrease Color exposure value" << std::endl; + std::cout << " inc color gain / cgi - increase Color gain value" << std::endl; + std::cout << " dec color gain / cgd - decrease Color gain value" << std::endl; + std::cout << " color mirror / cm - on/off color mirror" << std::endl; + std::cout << " depth ae / da - on/off Depth/IR auto exposure" << std::endl; + std::cout << " depth mirror / dm - on/off Depth mirror" << std::endl; + std::cout << " inc depth value / dei - increase Depth/IR exposure value" << std::endl; + std::cout << " dec depth value / ded - decrease Depth/IR exposure value" << std::endl; + std::cout << " inc depth gain / dgi - increase Depth/IR gain value" << std::endl; + std::cout << " dec depth gain / dgd - decrease Depth/IR gain value" << std::endl; + std::cout << " ir mirror / im - on/off Ir mirror" << std::endl; + if(irRightMirrorSupport) { + std::cout << " ir right mirror / irm - on/off Ir right mirror" << std::endl; + } + + std::cout << "--------------------------------" << std::endl; + std::cout << " help / ? - print usage" << std::endl; + std::cout << " quit / q- quit application" << std::endl; +} + +void commandProcess(std::string cmd) { + if(cmd == "info" || cmd == "i") { + getDeviceInformation(); + } + else if(cmd == "param" || cmd == "p") { + getCameraParams(); + } + else if(cmd == "laser" || cmd == "l") { + switchLaser(); + } + else if(cmd == "ldp" || cmd == "d") { + switchLDP(); + } + else if(cmd == "ldp status" || cmd == "ds") { + getLDPStatus(); + } + else if(cmd == "color ae" || cmd == "ca") { + switchColorAE(); + } + else if(cmd == "inc color value" || cmd == "cei") { + setColorExposureValue(true); + } + else if(cmd == "dec color value" || cmd == "ced") { + setColorExposureValue(false); + } + else if(cmd == "inc color gain" || cmd == "cgi") { + setColorGainValue(true); + } + else if(cmd == "dec color gain" || cmd == "cgd") { + setColorGainValue(false); + } + else if(cmd == "inc depth value" || cmd == "dei") { + setDepthExposureValue(true); + } + else if(cmd == "dec depth value" || cmd == "ded") { + setDepthExposureValue(false); + } + else if(cmd == "inc depth gain" || cmd == "dgi") { + setDepthGainValue(true); + } + else if(cmd == "dec depth gain" || cmd == "dgd") { + setDepthGainValue(false); + } + else if(cmd == "depth ae" || cmd == "da") { + switchDepthAE(); + } + else if(cmd == "color mirror" || cmd == "cm") { + switchColorMirror(); + } + else if(cmd == "depth mirror" || cmd == "dm") { + + switchDepthMirror(); + } + else if(cmd == "ir mirror" || cmd == "im") { + switchIRMirror(); + } + else if(cmd == " ir right mirror" || cmd == "irm") { + switchIRRightMirror(); + } + else if(cmd == "help" || cmd == "?") { + printUsage(); + } + else { + std::cerr << "Unsupported command received! Input \"help\" to get usage" << std::endl; + } +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.coordinate_transform/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.coordinate_transform/CMakeLists.txt new file mode 100644 index 0000000..53a75e6 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.coordinate_transform/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_coordinate_transform) + +add_executable(${PROJECT_NAME} coordinate_transform.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.coordinate_transform/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.coordinate_transform/README.md new file mode 100644 index 0000000..46b3b5c --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.coordinate_transform/README.md @@ -0,0 +1,98 @@ +# C++ Sample: 3.advanced.coordinate_transform + +## Overview + +Use the SDK interface to transform different coordinate systems. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions + +Frameset is a combination of different types of Frames + +## Code overview + +1. Enable color stream + + ```cpp + auto colorProfiles = pipe.getStreamProfileList(OB_SENSOR_COLOR); + if(colorProfiles) { + colorProfile = colorProfiles->getVideoStreamProfile(1280, OB_HEIGHT_ANY, OB_FORMAT_RGB, 30); + } + config->enableStream(colorProfile); + ``` + +2. Enable depth stream + + ```cpp + auto depthProfiles = pipe.getStreamProfileList(OB_SENSOR_DEPTH); + std::shared_ptr depthProfile = nullptr; + if(depthProfiles) { + depthProfile = depthProfiles->getVideoStreamProfile(640, OB_HEIGHT_ANY, OB_FORMAT_Y16, 30); + + } + config->enableStream(depthProfile); + ``` + +3. Get frame data + + ```cpp + auto colorFrame = frameSet->colorFrame(); + auto depthFrame = frameSet->depthFrame(); + ``` + +4. Get get stream profile + + ```cpp + auto colorProfile = colorFrame->getStreamProfile(); + auto depthProfile = depthFrame->getStreamProfile(); + ``` + +5. Get the extrinsic parameters + + ```cpp + auto extrinsicD2C = depthProfile->getExtrinsicTo(colorProfile); + auto extrinsicC2D = colorProfile->getExtrinsicTo(depthProfile); + ``` + +6. Get the intrinsic parameters + + ```cpp + auto colorIntrinsic = colorProfile->as()->getIntrinsic(); + auto colorDistortion = colorProfile->as()->getDistortion(); + ``` + +7. Get the distortion parameters + + ```cpp + auto depthIntrinsic = depthProfile->as()->getIntrinsic(); + auto depthDistortion = depthProfile->as()->getDistortion(); + ``` + +8. Processing + + ```cpp + if(testType == "1") { + transformation2dto2d(colorFrame, depthFrame); + } else if (testType == "2") { + transformation2dto3d(colorFrame, depthFrame); + } else if (testType == "3") { + transformation3dto3d(colorFrame, depthFrame); + } else if (testType == "4") { + transformation3dto2d(colorFrame, depthFrame); + } else { + std::cout << "Invalid command" << std::endl; + } + ``` + +## Run Sample + +Press the Esc key to exit the program. +Press the 1 key - transformation 2d to 2d +Press the 2 key - transformation 2d to 3d +Press the 3 key - transformation 3d to 3d +Press the 4 key - transformation 3d to 2d + +### Result + +![image](../../docs/resource/coordinate_transform.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.coordinate_transform/coordinate_transform.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.coordinate_transform/coordinate_transform.cpp new file mode 100644 index 0000000..3cd7c5e --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.coordinate_transform/coordinate_transform.cpp @@ -0,0 +1,299 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include +#include "libobsensor/hpp/Utils.hpp" +#include "libobsensor/hpp/Frame.hpp" + +void printUsage(); +void transformation2dto2d(std::shared_ptr colorFrame, std::shared_ptr depthFrame); +void transformation2dto3d(std::shared_ptr colorFrame, std::shared_ptr depthFrame); +void transformation3dto2d(std::shared_ptr colorFrame, std::shared_ptr depthFrame); +void transformation3dto3d(std::shared_ptr colorFrame, std::shared_ptr depthFrame); + +int main(void) try { + // Configure which streams to enable or disable for the Pipeline by creating a Config + auto config = std::make_shared(); + + // enable depth and color streams with specified format + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_ANY); + config->enableVideoStream(OB_STREAM_COLOR, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_ANY); + + // set the frame aggregate output mode to ensure all types of frames are included in the output frameset + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + + // Create a pipeline with default device to manage stream + auto pipe = std::make_shared(); + + // Start the pipeline with config + pipe->start(config); + while(1) { + printUsage(); + + std::cout << "\nInput command: "; + std::string cmd = "1"; + std::getline(std::cin, cmd); + if(cmd == "quit" || cmd == "q") { + break; + } + + // Wait for a frameset from the pipeline + auto frameSet = pipe->waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + + // Get the color frame and check its validity + auto colorFrame = frameSet->getFrame(OB_FRAME_COLOR); + + // Get the depth frame and check its validity + auto depthFrame = frameSet->getFrame(OB_FRAME_DEPTH); + + if(cmd == "1") { + transformation2dto2d(colorFrame, depthFrame); + } + else if(cmd == "2") { + transformation2dto3d(colorFrame, depthFrame); + } + else if(cmd == "3") { + transformation3dto3d(colorFrame, depthFrame); + } + else if(cmd == "4") { + transformation3dto2d(colorFrame, depthFrame); + } + else { + std::cout << "Invalid command" << std::endl; + } + } + + pipe->stop(); + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + exit(EXIT_FAILURE); +} +void printUsage() { + std::cout << "Support commands:" << std::endl; + std::cout << " 1 - transformation 2d to 2d" << std::endl; + std::cout << " 2 - transformation 2d to 3d" << std::endl; + std::cout << " 3 - transformation 3d to 3d" << std::endl; + std::cout << " 4 - transformation 3d to 2d" << std::endl; + + std::cout << "--------------------------------" << std::endl; + std::cout << " quit / q- quit application" << std::endl; +} + +void printRuslt(std::string msg, OBPoint2f sourcePixel, OBPoint2f targetPixel) { + std::cout << msg << ":" << "(" << sourcePixel.x << ", " << sourcePixel.y << ") -> (" << targetPixel.x << ", " << targetPixel.y << ")" << std::endl; +} + +void printRuslt(std::string msg, OBPoint2f sourcePixel, OBPoint3f targetPixel, float depthValue) { + std::cout << msg << ":" << "depth " << depthValue << " (" << sourcePixel.x << ", " << sourcePixel.y << ") -> (" << targetPixel.x << ", " << targetPixel.y << ", " << targetPixel.z << ")" << std::endl; +} + +void printRuslt(std::string msg, OBPoint3f sourcePixel, OBPoint2f targetPixel) { + std::cout << msg << ":" << "(" << sourcePixel.x << ", " << sourcePixel.y << ", " << sourcePixel.z << ") -> (" << targetPixel.x << ", " << targetPixel.y << ")" << std::endl; +} + +void printRuslt(std::string msg, OBPoint3f sourcePixel, OBPoint3f targetPixel) { + std::cout << msg << ":" << "(" << sourcePixel.x << ", " << sourcePixel.y << ", " << sourcePixel.z << ") -> (" << targetPixel.x << ", " << targetPixel.y << ", " << targetPixel.z << ")" << std::endl; +} + +// test the transformation from one 2D coordinate system to another +void transformation2dto2d(std::shared_ptr colorFrame, std::shared_ptr depthFrame) { + // Get the width and height of the color and depth frames + auto colorFrameWidth = colorFrame->as()->getWidth(); + auto depthFrameWidth = depthFrame->as()->getWidth(); + auto colorFrameHeight = colorFrame->as()->getHeight(); + auto depthFrameHeight = depthFrame->as()->getHeight(); + + // Get the stream profiles for the color and depth frames + auto colorProfile = colorFrame->getStreamProfile(); + auto depthProfile = depthFrame->getStreamProfile(); + auto extrinsicD2C = depthProfile->getExtrinsicTo(colorProfile); + + // Get the intrinsic and distortion parameters for the color and depth streams + auto colorIntrinsic = colorProfile->as()->getIntrinsic(); + auto colorDistortion = colorProfile->as()->getDistortion(); + auto depthIntrinsic = depthProfile->as()->getIntrinsic(); + auto depthDistortion = depthProfile->as()->getDistortion(); + // Access the depth data from the frame + uint16_t *pDepthData = (uint16_t *)depthFrame->getData(); + uint16_t convertAreaWidth = 3; + uint16_t convertAreaHeight = 3; + + // Transform depth values to the color frame's coordinate system + for(uint32_t i = depthFrameHeight / 2; i < (depthFrameHeight / 2 + convertAreaHeight); i++) { + for(uint32_t j = depthFrameWidth / 2; j < (depthFrameWidth / 2 + convertAreaWidth); j++) { + OBPoint2f sourcePixel = { static_cast(j), static_cast(i) }; + OBPoint2f targetPixel = {}; + float depthValue = (float)pDepthData[i * depthFrameWidth + j]; + if(depthValue == 0) { + std::cout << "The depth value is 0, so it's recommended to point the camera at a flat surface" << std::endl; + continue; + } + + // Demonstrate Depth 2D converted to Color 2D + bool result = ob::CoordinateTransformHelper::transformation2dto2d(sourcePixel, depthValue, depthIntrinsic, depthDistortion, colorIntrinsic, + colorDistortion, extrinsicD2C, &targetPixel); + + // Check transformation result and whether the target pixel is within the color frame + if(!result || targetPixel.y < 0 || targetPixel.x < 0 || targetPixel.y > colorFrameWidth || targetPixel.x > colorFrameWidth) { + continue; + } + + // Calculate the index position of the target pixel in the transformation data buffer + auto index = (((uint32_t)targetPixel.y * colorFrameWidth) + (uint32_t)targetPixel.x); + if(index > colorFrameWidth * colorFrameHeight) { + continue; + } + + printRuslt("depth to color: depth image coordinate transform to color image coordinate", sourcePixel, targetPixel); + } + } +} + +// test the transformation from 2D to 3D coordinates +void transformation2dto3d(std::shared_ptr colorFrame, std::shared_ptr depthFrame) { + // Get the width and height of the color and depth frames + auto depthFrameWidth = depthFrame->as()->getWidth(); + auto depthFrameHeight = depthFrame->as()->getHeight(); + + // Get the stream profiles for the color and depth frames + auto colorProfile = colorFrame->getStreamProfile(); + auto depthProfile = depthFrame->getStreamProfile(); + auto extrinsicD2C = depthProfile->getExtrinsicTo(colorProfile); + + // Get the intrinsic and distortion parameters for the color and depth streams + auto depthIntrinsic = depthProfile->as()->getIntrinsic(); + // Access the depth data from the frame + uint16_t *pDepthData = (uint16_t *)depthFrame->getData(); + uint16_t convertAreaWidth = 3; + uint16_t convertAreaHeight = 3; + + // Transform depth values to the color frame's coordinate system + for(uint32_t i = depthFrameHeight / 2; i < (depthFrameHeight / 2 + convertAreaHeight); i++) { + for(uint32_t j = depthFrameWidth / 2; j < (depthFrameWidth / 2 + convertAreaWidth); j++) { + // Get the coordinates of the current pixel + OBPoint2f sourcePixel = { static_cast(j), static_cast(i) }; + OBPoint3f targetPixel = {}; + // Get the depth value of the current pixel + float depthValue = (float)pDepthData[i * depthFrameWidth + j]; + if(depthValue == 0) { + std::cout << "The depth value is 0, so it's recommended to point the camera at a flat surface" << std::endl; + continue; + } + + // Perform the 2D to 3D transformation + bool result = ob::CoordinateTransformHelper::transformation2dto3d(sourcePixel, depthValue, depthIntrinsic, extrinsicD2C, &targetPixel); + if(!result ) { + continue; + } + + printRuslt("2d to 3D: pixel coordinates and depth transform to point in 3D space", sourcePixel, targetPixel, depthValue); + } + } +} + +// test the transformation from 3D coordinates to 3D coordinates +void transformation3dto3d(std::shared_ptr colorFrame, std::shared_ptr depthFrame) { + // Get the width and height of the color and depth frames + auto depthFrameWidth = depthFrame->as()->getWidth(); + auto depthFrameHeight = depthFrame->as()->getHeight(); + + // Get the stream profiles for the color and depth frames + auto colorProfile = colorFrame->getStreamProfile(); + auto depthProfile = depthFrame->getStreamProfile(); + auto extrinsicD2C = depthProfile->getExtrinsicTo(colorProfile); + auto extrinsicC2D = colorProfile->getExtrinsicTo(depthProfile); + + // Get the intrinsic and distortion parameters for the color and depth streams + auto depthIntrinsic = depthProfile->as()->getIntrinsic(); + // Access the depth data from the frame + uint16_t *pDepthData = (uint16_t *)depthFrame->getData(); + uint16_t convertAreaWidth = 3; + uint16_t convertAreaHeight = 3; + + // Transform depth values to the color frame's coordinate system + for(uint32_t i = depthFrameHeight / 2; i < (depthFrameHeight / 2 + convertAreaHeight); i++) { + for(uint32_t j = depthFrameWidth / 2; j < (depthFrameWidth / 2 + convertAreaWidth); j++) { + // Get the coordinates of the current pixel + OBPoint2f sourcePixel = { static_cast(j), static_cast(i) }; + OBPoint3f tmpTargetPixel = {}; + OBPoint3f targetPixel = {}; + // Get the depth value of the current pixel + float depthValue = (float)pDepthData[i * depthFrameWidth + j]; + if(depthValue == 0) { + std::cout << "The depth value is 0, so it's recommended to point the camera at a flat surface" << std::endl; + continue; + } + + // Perform the 2D to 3D transformation + bool result = ob::CoordinateTransformHelper::transformation2dto3d(sourcePixel, depthValue, depthIntrinsic, extrinsicD2C, &tmpTargetPixel); + if(!result ) { + continue; + } + printRuslt("2d to 3D: pixel coordinates and depth transform to point in 3D space", sourcePixel, tmpTargetPixel, depthValue); + + // Perform the 3D to 3D transformation + result = ob::CoordinateTransformHelper::transformation3dto3d(tmpTargetPixel, extrinsicC2D, &targetPixel); + if(!result ) { + continue; + } + printRuslt("3d to 3D: transform 3D coordinates relative to one sensor to 3D coordinates relative to another viewpoint", tmpTargetPixel, targetPixel); + } + } +} + +// test the transformation from 3D coordinates back to 2D coordinates +void transformation3dto2d(std::shared_ptr colorFrame, std::shared_ptr depthFrame) { + // Get the width and height of the color and depth frames + auto depthFrameWidth = depthFrame->as()->getWidth(); + auto depthFrameHeight = depthFrame->as()->getHeight(); + + // Get the stream profiles for the color and depth frames + auto colorProfile = colorFrame->getStreamProfile(); + auto depthProfile = depthFrame->getStreamProfile(); + auto extrinsicD2C = depthProfile->getExtrinsicTo(colorProfile); + auto extrinsicC2D = colorProfile->getExtrinsicTo(depthProfile); + + // Get the intrinsic and distortion parameters for the color and depth streams + auto depthIntrinsic = depthProfile->as()->getIntrinsic(); + auto depthDistortion = depthProfile->as()->getDistortion(); + // Access the depth data from the frame + uint16_t *pDepthData = (uint16_t *)depthFrame->getData(); + uint16_t convertAreaWidth = 3; + uint16_t convertAreaHeight = 3; + + // Transform depth values to the color frame's coordinate system + for(uint32_t i = depthFrameHeight / 2; i < (depthFrameHeight / 2 + convertAreaHeight); i++) { + for(uint32_t j = depthFrameWidth / 2; j < (depthFrameWidth / 2 + convertAreaWidth); j++) { + // Get the coordinates of the current pixel + OBPoint2f sourcePixel = { static_cast(j), static_cast(i) }; + OBPoint3f tmpTargetPixel = {}; + OBPoint2f targetPixel = {}; + // Get the depth value of the current pixel + float depthValue = (float)pDepthData[i * depthFrameWidth + j]; + if(depthValue == 0) { + std::cout << "The depth value is 0, so it's recommended to point the camera at a flat surface" << std::endl; + continue; + } + + // Perform the 2D to 3D transformation + bool result = ob::CoordinateTransformHelper::transformation2dto3d(sourcePixel, depthValue, depthIntrinsic, + extrinsicD2C, &tmpTargetPixel); + if(!result ) { + continue; + } + printRuslt("depth 2d to 3D: pixel coordinates and depth transform to point in 3D space", sourcePixel, tmpTargetPixel, depthValue); + + // Perform the 3D to 2D transformation + result = ob::CoordinateTransformHelper::transformation3dto2d(tmpTargetPixel, depthIntrinsic, depthDistortion, extrinsicC2D, &targetPixel); + if(!result ) { + continue; + } + printRuslt("3d to depth 2d : point in 3D space transform to the corresponding pixel coordinates in an image", tmpTargetPixel, targetPixel); + } + } +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hdr/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hdr/CMakeLists.txt new file mode 100644 index 0000000..08e3de5 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hdr/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_hdr) + +add_executable(${PROJECT_NAME} hdr.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hdr/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hdr/README.md new file mode 100644 index 0000000..e6a62a7 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hdr/README.md @@ -0,0 +1,83 @@ +# C++ Sample: 3.advanced.hdr + +## Overview + +In this sample, user can get the HDR merge image. Also Allows the user to control the on-off of the HDR synthesis and whether the original image is displayed through the keyboard. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions + +Frameset is a combination of different types of Frames + +### Attentions + +> This Sample only supports Gemini330 series devices. + +## Code overview + +### 1. Check if the device supports HDR merge + +```c++ +if(!device->isPropertySupported(OB_STRUCT_DEPTH_HDR_CONFIG, OB_PERMISSION_READ_WRITE)) { + std::cerr << "Current default device does not support HDR merge" << std::endl; + std::cout << "Press any key to exit..."; + ob_smpl::waitForKeyPressed(); + return -1; +} +``` + +### 2. Get depth stream profile + +Get all stream profiles of the depth camera, including stream resolution, frame rate, and frame format + +```c++ +auto depthProfiles = pipe.getStreamProfileList(OB_SENSOR_DEPTH); +auto depthProfile = depthProfiles->getProfile(OB_PROFILE_DEFAULT); +config->enableStream(depthProfile); +``` + +### 3. Create HDRMerge + +Create HDRMerge post processor to merge depth frames betweens different hdr sequence ids. +The HDRMerge also supports processing of infrared frames. + +```c++ +auto hdrMerge = ob::FilterFactory::createFilter("HDRMerge"); +``` + +### 5. Configure and enable Hdr stream + +```c++ + OBHdrConfig obHdrConfig; + obHdrConfig.enable = true; // enable HDR merge + obHdrConfig.exposure_1 = 7500; + obHdrConfig.gain_1 = 24; + obHdrConfig.exposure_2 = 100; + obHdrConfig.gain_2 = 16; + device->setStructuredData(OB_STRUCT_DEPTH_HDR_CONFIG, reinterpret_cast(&obHdrConfig), sizeof(OBHdrConfig)); +``` + +### 7. Stop the pipeline and close hdr merge + +```c++ +// Stop the Pipeline, no frame data will be generated +pipe.stop(); + +// close hdr merge +obHdrConfig.enable = false; +device->setStructuredData(OB_STRUCT_DEPTH_HDR_CONFIG, reinterpret_cast(&obHdrConfig), sizeof(OBHdrConfig)); +``` + +## Run Sample + +### Key introduction + +Press the 'Esc' key in the window to exit the program. +Press the '?' key in the window to show key map. +Press the 'M' key in the window to Toggle HDR merge. +Press the 'N' key in the window to Toggle alternate show origin frame. + +### Result + +![hdr](../../docs/resource/hdr.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hdr/hdr.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hdr/hdr.cpp new file mode 100644 index 0000000..50e672d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hdr/hdr.cpp @@ -0,0 +1,129 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +int main(void) try { + // Create a pipeline with default device + ob::Pipeline pipe; + + // Get the device from the pipeline + auto device = pipe.getDevice(); + + // Check if the device supports HDR merge + if(!device->isPropertySupported(OB_STRUCT_DEPTH_HDR_CONFIG, OB_PERMISSION_READ_WRITE)) { + std::cerr << "Current default device does not support HDR merge" << std::endl; + std::cout << "Press any key to exit..."; + ob_smpl::waitForKeyPressed(); + return -1; + } + + // Configure which streams to enable or disable for the Pipeline by creating a Config + std::shared_ptr config = std::make_shared(); + + // enable depth stream with default profile + config->enableVideoStream(OB_STREAM_DEPTH); + config->enableVideoStream(OB_STREAM_IR_LEFT); + config->enableVideoStream(OB_STREAM_IR_RIGHT); + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + + // Create HDRMerge post processor to merge depth frames betweens different hdr sequence ids. + // The HDRMerge also supports processing of infrared frames. + auto hdrMerge = ob::FilterFactory::createFilter("HDRMerge"); + if( device->isFrameInterleaveSupported() ) { + // load frame interleave mode as 'Depth from HDR' + device->loadFrameInterleave("Depth from HDR"); + // enable frame interleave mode + device->setBoolProperty(OB_PROP_FRAME_INTERLEAVE_ENABLE_BOOL, true); + + // The default parameters were loaded when loadFrameInterleave is called + // You can also modify these parameters yourself + // + // 1. frame interleave parameters for index 0(index starts from 0): + // device->setIntProperty(OB_PROP_FRAME_INTERLEAVE_CONFIG_INDEX_INT, 0); + // device->setIntProperty(OB_PROP_LASER_CONTROL_INT, 1); // laser control must be 1 + // device->setIntProperty(OB_PROP_DEPTH_EXPOSURE_INT, 7500); + // device->setIntProperty(OB_PROP_DEPTH_GAIN_INT, 16); + // device->setIntProperty(OB_PROP_IR_BRIGHTNESS_INT, 60); + // device->setIntProperty(OB_PROP_IR_AE_MAX_EXPOSURE_INT, 10000); + + // 2. frame interleave parameters for index 1(index starts from 0): + // device->setIntProperty(OB_PROP_FRAME_INTERLEAVE_CONFIG_INDEX_INT, 1); + // device->setIntProperty(OB_PROP_LASER_CONTROL_INT, 1); // laser control must be 1 + // device->setIntProperty(OB_PROP_DEPTH_EXPOSURE_INT, 1); + // device->setIntProperty(OB_PROP_DEPTH_GAIN_INT, 16); + // device->setIntProperty(OB_PROP_IR_BRIGHTNESS_INT, 20); + // device->setIntProperty(OB_PROP_IR_AE_MAX_EXPOSURE_INT, 2000); + }else { + // configure and enable Hdr stream + OBHdrConfig obHdrConfig; + obHdrConfig.enable = true; // enable HDR merge + obHdrConfig.exposure_1 = 7500; + obHdrConfig.gain_1 = 24; + obHdrConfig.exposure_2 = 100; + obHdrConfig.gain_2 = 16; + device->setStructuredData(OB_STRUCT_DEPTH_HDR_CONFIG, reinterpret_cast(&obHdrConfig), sizeof(OBHdrConfig)); + } + + // Start the pipeline with config + pipe.start(config); + + // Create a window for rendering and set the resolution of the window + ob_smpl::CVWindow win("HDR-Merge", 1280, 720, ob_smpl::ARRANGE_GRID); + win.addLog("The HDR-Merged depth frames are displayed in the last row of the window."); + while(win.run()) { + auto frameSet = pipe.waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + + // Get the depth and infrared frames from the frameset + auto depthFrame = frameSet->getFrame(OB_FRAME_DEPTH)->as(); + auto leftIRFrame = frameSet->getFrame(OB_FRAME_IR_LEFT)->as(); + auto rightIRFrame = frameSet->getFrame(OB_FRAME_IR_RIGHT)->as(); + + // Get the HDR sequence id from the depth frame metadata + int groupId = static_cast(depthFrame->getMetadataValue(OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_INDEX)); + win.pushFramesToView({ depthFrame, leftIRFrame, rightIRFrame }, groupId); + + try { + // Using HDRMerge filter to merge hdr frames + auto result = hdrMerge->process(frameSet); + if(result == nullptr) { + continue; + } + auto resultFrameSet = result->as(); + auto resultDepthFrame = resultFrameSet->getFrame(OB_FRAME_DEPTH)->as(); + + // add merged depth frame to render queue + win.pushFramesToView(resultDepthFrame, 10); // set the group id to 10 to avoid same group id with original depth frame + } + catch(ob::Error &e) { + std::cerr << "HDRMerge error: " << e.what() << std::endl; + } + } + + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + + // close hdr merge + if(device->isFrameInterleaveSupported()) { + device->setBoolProperty(OB_PROP_FRAME_INTERLEAVE_ENABLE_BOOL, false); + } + else { + OBHdrConfig obHdrConfig = { 0 }; + obHdrConfig.enable = false; + device->setStructuredData(OB_STRUCT_DEPTH_HDR_CONFIG, reinterpret_cast(&obHdrConfig), sizeof(OBHdrConfig)); + } + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit..."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hw_d2c_align/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hw_d2c_align/CMakeLists.txt new file mode 100644 index 0000000..415f880 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hw_d2c_align/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_hw_d2c_align) + +add_executable(${PROJECT_NAME} hw_d2c_align.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hw_d2c_align/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hw_d2c_align/README.md new file mode 100644 index 0000000..309079c --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hw_d2c_align/README.md @@ -0,0 +1,14 @@ +# C++ Sample: 3.advanced.hw_d2c_align + +## Overview + +This sample demonstrates how to use the SDK to enable depth-to-color alignment on hardware devices (alse known as hardware D2C or HwD2C). + +### Knowledge + +- The HwD2C feature allows you to align the depth image to the color image captured by the device. This is useful for applications that require depth frame to be aligned with color frame and do want to use increase the resource usage of the host. + +### Attention + +- The HwD2C feature is only available on devices that support it. Please check the documentation of your device to see if it supports HwD2C. +- Is not all profile of depth stream are supported the HwD2C feature. Please call the `getD2CDepthProfileList` function of `ob::Pipeline` class to check the supported depth profile. \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hw_d2c_align/hw_d2c_align.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hw_d2c_align/hw_d2c_align.cpp new file mode 100644 index 0000000..1270997 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.hw_d2c_align/hw_d2c_align.cpp @@ -0,0 +1,139 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include +#include + +bool enable_align_mode = 1; + +// key press event processing +void handleKeyPress(ob_smpl::CVWindow &win, std::shared_ptr pipe, int key, std::shared_ptr config) { + if(key == 't' || key == 'T') { + // Press the T key to switch align mode + enable_align_mode = !enable_align_mode; + + // update the align mode in the config + if(enable_align_mode) { + config->setAlignMode(ALIGN_D2C_HW_MODE); + win.addLog("Haeware Depth to Color Align: Enabled"); + } + else { + config->setAlignMode(ALIGN_DISABLE); + win.addLog("Haeware Depth to Color Align: Disabled"); + } + + // restart the pipeline with the new config + pipe->stop(); + pipe->start(config); + } +} + +// check if the given stream profiles support hardware depth-to-color alignment +bool checkIfSupportHWD2CAlign(std::shared_ptr pipe, std::shared_ptr colorStreamProfile, + std::shared_ptr depthStreamProfile) { + auto hwD2CSupportedDepthStreamProfiles = pipe->getD2CDepthProfileList(colorStreamProfile, ALIGN_D2C_HW_MODE); + if(hwD2CSupportedDepthStreamProfiles->count() == 0) { + return false; + } + + // Iterate through the supported depth stream profiles and check if there is a match with the given depth stream profile + auto depthVsp = depthStreamProfile->as(); + auto count = hwD2CSupportedDepthStreamProfiles->getCount(); + for(uint32_t i = 0; i < count; i++) { + auto sp = hwD2CSupportedDepthStreamProfiles->getProfile(i); + auto vsp = sp->as(); + if(vsp->getWidth() == depthVsp->getWidth() && vsp->getHeight() == depthVsp->getHeight() && vsp->getFormat() == depthVsp->getFormat() + && vsp->getFps() == depthVsp->getFps()) { + // Found a matching depth stream profile, it is means the given stream profiles support hardware depth-to-color alignment + return true; + } + } + return false; +} + +// create a config for hardware depth-to-color alignment +std::shared_ptr createHwD2CAlignConfig(std::shared_ptr pipe) { + auto coloStreamProfiles = pipe->getStreamProfileList(OB_SENSOR_COLOR); + auto depthStreamProfiles = pipe->getStreamProfileList(OB_SENSOR_DEPTH); + + // Iterate through all color and depth stream profiles to find a match for hardware depth-to-color alignment + auto colorSpCount = coloStreamProfiles->getCount(); + auto depthSpCount = depthStreamProfiles->getCount(); + for(uint32_t i = 0; i < colorSpCount; i++) { + auto colorProfile = coloStreamProfiles->getProfile(i); + auto colorVsp = colorProfile->as(); + for(uint32_t j = 0; j < depthSpCount; j++) { + auto depthProfile = depthStreamProfiles->getProfile(j); + auto depthVsp = depthProfile->as(); + + // make sure the color and depth stream have the same fps, due to some models may not support different fps + if(colorVsp->getFps() != depthVsp->getFps()) { + continue; + } + + // Check if the given stream profiles support hardware depth-to-color alignment + if(checkIfSupportHWD2CAlign(pipe, colorProfile, depthProfile)) { + // If support, create a config for hardware depth-to-color alignment + auto hwD2CAlignConfig = std::make_shared(); + hwD2CAlignConfig->enableStream(colorProfile); // enable color stream + hwD2CAlignConfig->enableStream(depthProfile); // enable depth stream + hwD2CAlignConfig->setAlignMode(ALIGN_D2C_HW_MODE); // enable hardware depth-to-color alignment + hwD2CAlignConfig->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); // output frameset with all types of frames + return hwD2CAlignConfig; + } + } + } + return nullptr; +} + +int main(void) try { + // Create a pipeline with default device to manage stream + auto pipe = std::make_shared(); + + // enable frame sync inside the pipeline, which is synchronized by frame timestamp + pipe->enableFrameSync(); + + // Create a config for hardware depth-to-color alignment + auto config = createHwD2CAlignConfig(pipe); + if(config == nullptr) { + std::cerr << "Current device does not support hardware depth-to-color alignment." << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); + } + + // Start the pipeline with config + pipe->start(config); + + // Create a window for rendering and set the resolution of the window + ob_smpl::CVWindow win("Hardware Depth to Color Align", 1280, 720, ob_smpl::ARRANGE_OVERLAY); + // set key prompt + win.setKeyPrompt("'T': Enable/Disable HwD2C, '+/-': Adjust Transparency"); + // set the callback function for the window to handle key press events + win.setKeyPressedCallback([&](int key) { handleKeyPress(win, pipe, key, config); }); + + while(win.run()) { + // Wait for a frameset from the pipeline + auto frameSet = pipe->waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + win.pushFramesToView(frameSet); + } + + // Stop the Pipeline, no frame data will be generated + pipe->stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.laser_interleave/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.laser_interleave/CMakeLists.txt new file mode 100644 index 0000000..d7e95e0 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.laser_interleave/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_laser_interleave) + +add_executable(${PROJECT_NAME} laser_interleave.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.laser_interleave/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.laser_interleave/README.md new file mode 100644 index 0000000..0891a51 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.laser_interleave/README.md @@ -0,0 +1,108 @@ +# C++ Sample: 3.advanced.interleave + +## Overview + +In this sample, user can enable or disable the function of laser frame interleave and SequenceId filter. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions + +Frameset is a combination of different types of Frames + +### Attentions + +> This Sample only supports Gemini330 series devices. + +## Code overview + +### 1. Check if the device supports frame interleave + +```c++ + if(!device->isFrameInterleaveSupported()) { + std::cerr << "Current default device does not support frame interleave" << std::endl; + std::cout << "Press any key to exit..."; + ob_smpl::waitForKeyPressed(); + return -1; + } +``` + +### 2. Enable depth and IR stream + +Enable depth camera, left IR camera and right IR camera with default profiles + +```c++ + std::shared_ptr config = std::make_shared(); + // enable depth stream with default profile + config->enableVideoStream(OB_STREAM_DEPTH); + config->enableVideoStream(OB_STREAM_IR_LEFT); + config->enableVideoStream(OB_STREAM_IR_RIGHT); + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); +``` + +### 3. Create SequenceId filter + +Create SequenceId post processor to filter frames. + +```c++ + auto postDepthFilter = ob::FilterFactory::createFilter("SequenceIdFilter"); + auto postLeftInfraredFilter = ob::FilterFactory::createFilter("SequenceIdFilter"); + auto postRightInfraredFilter = ob::FilterFactory::createFilter("SequenceIdFilter"); +``` + +### 5. Enable laser frame laser interleave + +```c++ + // load frame interleave mode as 'Laser On-Off' + device->loadFrameInterleave("Laser On-Off"); + // enable frame interleave + device->setBoolProperty(OB_PROP_FRAME_INTERLEAVE_ENABLE_BOOL, true); + + // The default parameters were loaded when loadFrameInterleave is called + // You can also modify these parameters yourself + // + // 1. frame interleave parameters for index 1(index starts from 0): + // device->setIntProperty(OB_PROP_FRAME_INTERLEAVE_CONFIG_INDEX_INT, 1); + // device->setIntProperty(OB_PROP_LASER_CONTROL_INT, 0); // first: set laser control to 0 to turn off laser + // device->setIntProperty(OB_PROP_DEPTH_EXPOSURE_INT, 3000); + // device->setIntProperty(OB_PROP_DEPTH_GAIN_INT, 16); + // device->setIntProperty(OB_PROP_IR_BRIGHTNESS_INT, 60); + // device->setIntProperty(OB_PROP_IR_AE_MAX_EXPOSURE_INT, 17000); + + // 2. frame interleave parameters for index 0(index starts from 0): + // device->setIntProperty(OB_PROP_FRAME_INTERLEAVE_CONFIG_INDEX_INT, 0); + // device->setIntProperty(OB_PROP_LASER_CONTROL_INT, 1); // second: set laser control to 1 to turn on laser + // device->setIntProperty(OB_PROP_DEPTH_EXPOSURE_INT, 3000); + // device->setIntProperty(OB_PROP_DEPTH_GAIN_INT, 16); + // device->setIntProperty(OB_PROP_IR_BRIGHTNESS_INT, 60); + // device->setIntProperty(OB_PROP_IR_AE_MAX_EXPOSURE_INT, 30000); +``` + +### 7. Stop the pipeline and close frame interleave + +```c++ + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + +// close hdr merge + device->setBoolProperty(OB_PROP_FRAME_INTERLEAVE_ENABLE_BOOL, false); +``` + +## Run Sample + +### Key introduction + +Press the 'Esc' key in the window to exit the program. +Press the '?' key in the window to show key map. + +### Result + +After enabling laser frame interleave and SequenceId filter, +the user can set the sequence id of depth to 0 and the sequence id of IR to 1, +which will enhance the depth image quality and get the pure IR image. + +1. laser interleave when sequence id is 0 +![laser interleave](../../docs/resource/laser_interleave0.jpg) + +2. laser interleave when sequence id is 1 +![laser interleave](../../docs/resource/laser_interleave1.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.laser_interleave/laser_interleave.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.laser_interleave/laser_interleave.cpp new file mode 100644 index 0000000..70db2b2 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.laser_interleave/laser_interleave.cpp @@ -0,0 +1,226 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +void inputWatcher(); + +std::shared_ptr postDepthFilter = nullptr; +std::shared_ptr postLeftInfraredFilter = nullptr; +std::shared_ptr postRightInfraredFilter = nullptr; +std::shared_ptr win; + +int main(void) try { + // Create a pipeline with default device + ob::Pipeline pipe; + + // Get the device from the pipeline + auto device = pipe.getDevice(); + + // Check if the device supports frame interleave + if(!device->isFrameInterleaveSupported()) { + std::cerr << "Current default device does not support frame interleave" << std::endl; + std::cout << "Press any key to exit..."; + ob_smpl::waitForKeyPressed(); + return -1; + } + + // Configure which streams to enable or disable for the Pipeline by creating a Config + std::shared_ptr config = std::make_shared(); + + // enable depth stream with default profile + config->enableVideoStream(OB_STREAM_DEPTH); + config->enableVideoStream(OB_STREAM_IR_LEFT); + config->enableVideoStream(OB_STREAM_IR_RIGHT); + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + + // Create SequenceIdFilter post processor to filter frames. + // The SequenceIdFilter also supports processing of infrared frames. + postDepthFilter = ob::FilterFactory::createFilter("SequenceIdFilter"); + postLeftInfraredFilter = ob::FilterFactory::createFilter("SequenceIdFilter"); + postRightInfraredFilter = ob::FilterFactory::createFilter("SequenceIdFilter"); + + // load frame interleave mode as 'Laser On-Off' + device->loadFrameInterleave("Laser On-Off"); + // enable frame interleave + device->setBoolProperty(OB_PROP_FRAME_INTERLEAVE_ENABLE_BOOL, true); + + // The default parameters were loaded when loadFrameInterleave is called + // You can also modify these parameters yourself + // + // 1. frame interleave parameters for index 0(index starts from 0): + // device->setIntProperty(OB_PROP_FRAME_INTERLEAVE_CONFIG_INDEX_INT, 0); + // device->setIntProperty(OB_PROP_LASER_CONTROL_INT, 1); // first: set laser control to 1 to turn on laser + // device->setIntProperty(OB_PROP_DEPTH_EXPOSURE_INT, 3000); + // device->setIntProperty(OB_PROP_DEPTH_GAIN_INT, 16); + // device->setIntProperty(OB_PROP_IR_BRIGHTNESS_INT, 60); + // device->setIntProperty(OB_PROP_IR_AE_MAX_EXPOSURE_INT, 30000); + + // 2. frame interleave parameters for index 1(index starts from 0): + // device->setIntProperty(OB_PROP_FRAME_INTERLEAVE_CONFIG_INDEX_INT, 1); + // device->setIntProperty(OB_PROP_LASER_CONTROL_INT, 0); // second: set laser control to 0 to turn off laser + // device->setIntProperty(OB_PROP_DEPTH_EXPOSURE_INT, 3000); + // device->setIntProperty(OB_PROP_DEPTH_GAIN_INT, 16); + // device->setIntProperty(OB_PROP_IR_BRIGHTNESS_INT, 60); + // device->setIntProperty(OB_PROP_IR_AE_MAX_EXPOSURE_INT, 17000); + + // Start the pipeline with config + pipe.start(config); + + postDepthFilter->setConfigValue("sequenceid", -1); // sequenceid can be -1,0,1 + postLeftInfraredFilter->setConfigValue("sequenceid", -1); + postRightInfraredFilter->setConfigValue("sequenceid", -1); + + auto inputWatchThread = std::thread(inputWatcher); + inputWatchThread.detach(); + + // Create a window for rendering and set the resolution of the window + + // create window for render + win = std::make_shared("Laser On-Off", 1280, 720, ob_smpl::ARRANGE_GRID); + while(win->run()) { + auto frameSet = pipe.waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + + auto postFilter = [](std::shared_ptr frameSet, std::shared_ptr &filter, OBFrameType frameType) -> std::shared_ptr { + auto tempFrame = frameSet->getFrame(frameType); + if(!tempFrame) { + return nullptr; + } + return filter->process(tempFrame); + }; + + try { + // Using SequenceId filter to filter frames + + // 1: depth + auto depthFrame = postFilter(frameSet, postDepthFilter, OB_FRAME_DEPTH); + if(depthFrame) { + // add frame to render queue + win->pushFramesToView(depthFrame, 0); + } + + // 2: left infrared + auto leftIrFrame = postFilter(frameSet, postLeftInfraredFilter, OB_FRAME_IR_LEFT); + if(leftIrFrame) { + // add frame to render queue + win->pushFramesToView(leftIrFrame, 1); + } + + // 2: right infrared + auto rightIrFrame = postFilter(frameSet, postRightInfraredFilter, OB_FRAME_IR_RIGHT); + if(rightIrFrame) { + // add frame to render queue + win->pushFramesToView(rightIrFrame, 2); + } + } + catch(ob::Error &e) { + std::cerr << "SequenceIdFilter error: " << e.what() << std::endl; + } + } + + postDepthFilter.reset(); + postLeftInfraredFilter.reset(); + postRightInfraredFilter.reset(); + + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + + // close frame interleave + device->setBoolProperty(OB_PROP_FRAME_INTERLEAVE_ENABLE_BOOL, false); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit..."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +void printCommandTips() { + std::cout << "\n-------------------------------"; + std::cout << "\nCommand usage: "; + std::cout << "\n : stream filter name, must be one of the following values:"; + std::cout << "\n depth"; + std::cout << "\n left_ir"; + std::cout << "\n right_ir"; + std::cout << "\n : stream filter param, must be one of the following values:"; + std::cout << "\n all: disable sequenceid filter"; + std::cout << "\n 0: set sequenceid to 0"; + std::cout << "\n 1: set sequenceid to 1"; + std::cout << "\nPress 'q' or 'quit' to exit the program." << std::endl; +} + +void inputWatcher() { + while(true) { + std::string cmd; + + printCommandTips(); + std::getline(std::cin, cmd); + if(cmd == "quit" || cmd == "q") { + win->close(); + break; + } + else { + std::istringstream ss(cmd); + std::string tmp; + std::vector controlVec; + while(ss >> tmp) { + controlVec.push_back(tmp); + } + + if(controlVec.size() != 2) { + std::cerr << "Error: invalid param." << std::endl; + continue; + } + + // filter + std::shared_ptr filter = nullptr; + if(controlVec.at(0) == "depth") { + filter = postDepthFilter; + } + else if(controlVec.at(0) == "left_ir") { + filter = postLeftInfraredFilter; + } + else if(controlVec.at(0) == "right_ir") { + filter = postRightInfraredFilter; + } + else { + std::cerr << "Error: invalid param." << std::endl; + continue; + } + + // param + int32_t sequenceid = 0; + + if(controlVec.at(1) == "all") { + sequenceid = -1; + } + else if(controlVec.at(1) == "0") { + sequenceid = 0; + } + else if(controlVec.at(1) == "1") { + sequenceid = 1; + } + else { + std::cerr << "Error: invalid param." << std::endl; + continue; + } + + // set filter + try { + filter->setConfigValue("sequenceid", sequenceid); + std::cout << "Set sequenceid successfully" << std::endl; + } + catch(ob::Error &e) { + std::cerr << "Set sequenceid error: " << e.what() << std::endl; + } + } + } +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices/CMakeLists.txt new file mode 100644 index 0000000..39b21fa --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_multi_device) + +add_executable(${PROJECT_NAME} multi_device.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices/README.md new file mode 100644 index 0000000..29b1cbf --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices/README.md @@ -0,0 +1,92 @@ +# C++ Sample:3.advanced.multi_devices + +## Overview + +In this sample, users can connect multiple camera devices and get color and depth images of different cameras. + +### Knowledge + +Context is the environment context, the first object created during initialization, which can be used to perform some settings, including but not limited to device status change callbacks, log level settings, etc. Context can access multiple Devices. + +FrameSet is a combination of different types of Frames. + +## code overview + +1. StartStream DeviceIndex is used to identify different devices, and map is used to store the color and depth stream of different devices. + + ```cpp + void StartStream(std::map> &pipes) { + + for(auto &item: pipes) { + int deviceIndex = item.first; + auto &pipe = item.second; + + // config to enable depth and color streams + std::shared_ptr config = std::make_shared(); + config->enableVideoStream(OB_STREAM_COLOR); + config->enableVideoStream(OB_STREAM_DEPTH); + + // start pipeline and pass the callback function to receive the frames + pipe->start(config, [deviceIndex](std::shared_ptr frameSet) { + std::lock_guard lock(framesetMutex); + framesets[deviceIndex] = frameSet; + }); + } + } + ``` + +2. StopStream Obtain pipelines corresponding to different devices and stop the pipelines. + + ```cpp + void StopStream(std::map> &pipes) { + for(auto &item: pipes) { + auto &pipe = item.second; + // stop the pipeline + pipe->stop(); + } + + std::lock_guard lock(framesetMutex); + framesets.clear(); + } + ``` + +3. Obtain the currently connected devices through context, and obtain the list and number of devices. + + ```cpp + // Create a Context + ob::Context ctx; + + // Query the list of connected devices + auto devList = ctx.queryDeviceList(); + + // Get the number of connected devices + int devCount = devList->getCount(); + ``` + +4. Use map to bind deviceIndex to the device's pipeline to distinguish video streams obtained by different devices. + + ```cpp + // Create a pipeline for each device + std::map> pipes; + for(int i = 0; i < devCount; i++) { + // Get the device from device list + auto dev = devList->getDevice(i); + + // Create a pipeline for the device + auto pipe = std::make_shared(dev); + + // Add the pipeline to the map of pipelines + pipes.insert({ i, pipe }); + } + ``` + +## Run Sample + +### Key introduction + +Press the 'Esc' key in the window to exit the program. +Press the '?' key in the window to show key map. + +### Result + +![multi_devices](../../docs/resource/multi_devices.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices/multi_device.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices/multi_device.cpp new file mode 100644 index 0000000..507d99d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices/multi_device.cpp @@ -0,0 +1,104 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include + +std::map> framesets; +std::mutex framesetMutex; + +void startStream(std::map> &pipes) { + + for(auto &item: pipes) { + int deviceIndex = item.first; + auto &pipe = item.second; + + // config to enable depth and color streams + std::shared_ptr config = std::make_shared(); + config->enableVideoStream(OB_STREAM_DEPTH); + + auto sensorList = pipe->getDevice()->getSensorList(); + for(uint32_t index = 0; index < sensorList->getCount(); index++) { + OBSensorType sensorType = sensorList->getSensorType(index); + if(sensorType == OB_SENSOR_COLOR) { + config->enableVideoStream(OB_STREAM_COLOR); + } + } + + // start pipeline and pass the callback function to receive the frames + pipe->start(config, [deviceIndex](std::shared_ptr frameSet) { + std::lock_guard lock(framesetMutex); + framesets[deviceIndex] = frameSet; + }); + } +} + +void stopStream(std::map> &pipes) { + for(auto &item: pipes) { + auto &pipe = item.second; + // stop the pipeline + pipe->stop(); + } + + std::lock_guard lock(framesetMutex); + framesets.clear(); +} + +int main() try { + // Create a Context + ob::Context ctx; + + // Query the list of connected devices + auto devList = ctx.queryDeviceList(); + + // Get the number of connected devices + int devCount = devList->getCount(); + + // Create a pipeline for each device + std::map> pipes; + for(int i = 0; i < devCount; i++) { + // Get the device from device list + auto dev = devList->getDevice(i); + + // Create a pipeline for the device + auto pipe = std::make_shared(dev); + + // Add the pipeline to the map of pipelines + pipes.insert({ i, pipe }); + } + + // Start the depth and color streams for all devices + startStream(pipes); + + // Create a window for rendering and set the resolution of the window + ob_smpl::CVWindow win("MultiDevice", 1280, 720, ob_smpl::ARRANGE_GRID); + + // Main loop to show the frames, press `ESC` to exit + while(win.run()) { + // Get the latest frames from all devices + for(auto &item: framesets) { + std::lock_guard lock(framesetMutex); + auto deviceIndex = item.first; + auto &frameset = item.second; + + // push the frames to the window for show + win.pushFramesToView(frameset, deviceIndex); + } + } + + // Stop all streams and clear the framesets + stopStream(pipes); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/CMakeLists.txt new file mode 100644 index 0000000..4be70d9 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_multi_devices_sync) + +file(GLOB_RECURSE SOURCE_FILES *.cpp) +file(GLOB_RECURSE HEADER_FILES *.hpp) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES} ${HEADER_FILES} utils/cJSON.c) + +#add_executable(${PROJECT_NAME} ob_multi_devices_sync.cpp PipelineHolder.cpp utils/cJSON.c) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) + +find_package(Threads REQUIRED) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils Threads::Threads) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(FILES ${CMAKE_CURRENT_LIST_DIR}/MultiDeviceSyncConfig.json DESTINATION bin) +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/FramePairingManager.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/FramePairingManager.cpp new file mode 100644 index 0000000..c6326ba --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/FramePairingManager.cpp @@ -0,0 +1,151 @@ +#include "FramePairingManager.hpp" +#include +#include + + +uint64_t getFrameTimestampMsec(const std::shared_ptr frame) { + return frame->getTimeStampUs() / 1000; +} + + +FramePairingManager::FramePairingManager() + : destroy_(false) { + +} + +FramePairingManager::~FramePairingManager() { + release(); +} + +bool FramePairingManager::pipelineHoldersFrameNotEmpty() { + if(pipelineHolderList_.size() == 0) { + return false; + } + + for(const auto &holder: pipelineHolderList_) { + if(!holder->isFrameReady()) { + return false; + } + } + return true; +} + +void FramePairingManager::setPipelineHolderList(std::vector> pipelineHolderList) { + this->pipelineHolderList_ = pipelineHolderList; + for(auto &&pipelineHolder: pipelineHolderList) { + int deviceIndex = pipelineHolder->getDeviceIndex(); + if(pipelineHolder->getSensorType() == OB_SENSOR_DEPTH) { + depthPipelineHolderList_[deviceIndex] = pipelineHolder; + } + if(pipelineHolder->getSensorType() == OB_SENSOR_COLOR) { + colorPipelineHolderList_[deviceIndex] = pipelineHolder; + } + } +} + +std::vector, std::shared_ptr>> FramePairingManager::getFramePairs() { + std::vector, std::shared_ptr>> framePairs; + if(pipelineHolderList_.size() > 0) { + int depthPipelineHolderSize = static_cast(depthPipelineHolderList_.size()); + auto start = std::chrono::steady_clock::now(); + // Timestamp Matching Mode. + while(!pipelineHoldersFrameNotEmpty() && !destroy_) { + // Wait for frames if not yet available (optional: add sleep for simulation) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(now - start).count(); + if(elapsed > 200) { + return framePairs; + } + } + + if(destroy_) { + return framePairs; + } + + bool discardFrame = false; + + std::map> depthFramesMap; + std::map> colorFramesMap; + + std::vector> pipelineHolderVector; + sortFrameMap(pipelineHolderList_, pipelineHolderVector); + + auto refIter = pipelineHolderVector.begin(); + const auto &refHolder = *refIter; + auto refTsp = getFrameTimestampMsec(refHolder->frontFrame()); + auto refHalfTspGap = refHolder->halfTspGap; + for(const auto &item: pipelineHolderVector) { + auto tarFrame = item->frontFrame(); + auto tarHalfTspGap = item->halfTspGap; + int index = item->getDeviceIndex(); + auto frameType = item->getFrameType(); + uint32_t tspHalfGap = tarHalfTspGap > refHalfTspGap ? tarHalfTspGap : refHalfTspGap; + + // std::cout << "tspHalfGap : " << tspHalfGap << std::endl; + + auto tarTsp = getFrameTimestampMsec(tarFrame); + auto diffTsp = tarTsp - refTsp; + if(diffTsp > tspHalfGap) { + discardFrame = true; + //std::cout << "index = " << index << " frame type = " << frameType << " diff tsp = " << diffTsp << std::endl; + break; + } + + refHalfTspGap = tarHalfTspGap; + + if(frameType == OB_FRAME_DEPTH) { + depthFramesMap[index] = item->getFrame(); + } + if(frameType == OB_FRAME_COLOR) { + colorFramesMap[index] = item->getFrame(); + } + } + + if(discardFrame) { + depthFramesMap.clear(); + colorFramesMap.clear(); + return framePairs; + } + + std::cout << "=================================================" << std::endl; + + for(int i = 0; i < depthPipelineHolderSize; i++) { + auto depthFrame = depthFramesMap[i]; + auto colorFrame = colorFramesMap[i]; + std::cout << "Device#" << i << ", " + << " depth(us) " + << ", frame timestamp=" << depthFrame->timeStampUs() << "," + << "global timestamp = " << depthFrame->globalTimeStampUs() << "," + << "system timestamp = " << depthFrame->systemTimeStampUs() << std::endl; + + std::cout << "Device#" << i << ", " + << " color(us) " + << ", frame timestamp=" << colorFrame->timeStampUs() << "," + << "global timestamp = " << colorFrame->globalTimeStampUs() << "," + << "system timestamp = " << colorFrame->systemTimeStampUs() << std::endl; + + framePairs.emplace_back(depthFrame, colorFrame); + } + return framePairs; + } + + return framePairs; +} + +void FramePairingManager::sortFrameMap(std::vector> &pipelineHolders, + std::vector> &pipelineHolderVector) { + for(const auto &holder: pipelineHolders) { + pipelineHolderVector.push_back(holder); + } + + std::sort(pipelineHolderVector.begin(), pipelineHolderVector.end(), [](const std::shared_ptr &x, const std::shared_ptr &y) { + auto xTsp = getFrameTimestampMsec(x->frontFrame()); + auto yTsp = getFrameTimestampMsec(y->frontFrame()); + return xTsp < yTsp; + }); +} + +void FramePairingManager::release() { + destroy_ = true; +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/FramePairingManager.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/FramePairingManager.hpp new file mode 100644 index 0000000..e29d4aa --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/FramePairingManager.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "PipelineHolder.hpp" +#include +#include +#include +#include +#include + + +class FramePairingManager { +public: + FramePairingManager(); + ~FramePairingManager(); + +private: + bool pipelineHoldersFrameNotEmpty(); + void sortFrameMap(std::vector> &pipelineHolders, std::vector> &pipelineHolderVector); + +public: + void setPipelineHolderList(std::vector> pipelineHolderList); + + std::vector, std::shared_ptr>> getFramePairs(); + + void release(); + +private: + bool destroy_; + bool timestampPairingEnable_; + uint64_t timestampPairingRange_; + + std::vector> pipelineHolderList_; + std::map> depthPipelineHolderList_; + std::map> colorPipelineHolderList_; +}; diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/MultiDeviceSyncConfig.json b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/MultiDeviceSyncConfig.json new file mode 100644 index 0000000..416d088 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/MultiDeviceSyncConfig.json @@ -0,0 +1,30 @@ +{ + "version": "1.0.0", + "configTime": "2023/01/01", + "devices": [ + { + "sn": "CP2194200060", + "syncConfig": { + "syncMode": "OB_MULTI_DEVICE_SYNC_MODE_PRIMARY", + "depthDelayUs": 0, + "colorDelayUs": 0, + "trigger2ImageDelayUs": 0, + "triggerOutEnable": true, + "triggerOutDelayUs": 0, + "framesPerTrigger": 1 + } + }, + { + "sn": "CP0Y8420004K", + "syncConfig": { + "syncMode": "OB_MULTI_DEVICE_SYNC_MODE_SECONDARY", + "depthDelayUs": 0, + "colorDelayUs": 0, + "trigger2ImageDelayUs": 0, + "triggerOutEnable": true, + "triggerOutDelayUs": 0, + "framesPerTrigger": 1 + } + } + ] +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/PipelineHolder.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/PipelineHolder.cpp new file mode 100644 index 0000000..c58fa27 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/PipelineHolder.cpp @@ -0,0 +1,150 @@ +#include "PipelineHolder.hpp" + + +PipelineHolder::PipelineHolder(std::shared_ptr pipeline, OBSensorType sensorType, std::string deviceSN, int deviceIndex) + : startStream_(false), pipeline_(pipeline), sensorType_(sensorType), deviceSN_(deviceSN), deviceIndex_(deviceIndex) { +} + +PipelineHolder::~PipelineHolder() { + release(); +} + +void PipelineHolder::startStream() { + std::cout << "startStream: " << deviceSN_ << " sensorType:" << sensorType_ << std::endl; + try { + if(pipeline_) { + auto profileList = pipeline_->getStreamProfileList(sensorType_); + auto streamProfile = profileList->getProfile(OB_PROFILE_DEFAULT)->as(); + frameType_ = mapFrameType(sensorType_); + + auto fps = streamProfile->getFps(); + halfTspGap = static_cast(500.0f / fps + 0.5); + + std::shared_ptr config = std::make_shared(); + config->enableStream(streamProfile); + + pipeline_->start(config, [this](std::shared_ptr frameSet) { + processFrame(frameSet); + }); + startStream_ = true; + } + } + catch(ob::Error &e) { + std::cerr << "starting stream failed: " << deviceSN_ << std::endl; + handleStreamError(e); + } +} + +void PipelineHolder::processFrame(std::shared_ptr frameSet) { + if(!frameSet) { + std::cerr << "Invalid frameSet received." << std::endl; + return; + } + + if(!startStream_) { + return; + } + + { + std::lock_guard lock(queueMutex_); + auto obFrame = frameSet->getFrame(frameType_); + if(obFrame) { + if(obFrames.size() >= static_cast(maxFrameSize_)) { + obFrames.pop(); + } + obFrames.push(obFrame); + } + } + + condVar_.notify_all(); +} + +bool PipelineHolder::isFrameReady() { + { + std::unique_lock lock(queueMutex_); + condVar_.wait(lock, [this]() { return !obFrames.empty() || startStream_; }); + if(startStream_ && obFrames.empty()) { + return false; + } + } + return true; +} + +std::shared_ptr PipelineHolder::frontFrame() { + { + std::unique_lock lock(queueMutex_); + condVar_.wait(lock, [this]() { return !obFrames.empty() || startStream_; }); + if(startStream_ && obFrames.empty()) { + return nullptr; + } + auto frame = obFrames.front(); + return frame; + } +} + +void PipelineHolder::popFrame() { + { + std::unique_lock lock(queueMutex_); + condVar_.wait(lock, [this]() { return !obFrames.empty() || startStream_; }); + if(startStream_ && obFrames.empty()) { + return; + } + obFrames.pop(); + } +} + +std::shared_ptr PipelineHolder::getFrame() { + { + std::unique_lock lock(queueMutex_); + condVar_.wait(lock, [this]() { return !obFrames.empty() || startStream_; }); + if(startStream_ && obFrames.empty()) { + return nullptr; + } + auto frame = obFrames.front(); + obFrames.pop(); + return frame; + } +} + +void PipelineHolder::stopStream() { + try { + if(pipeline_) { + std::cout << "stopStream: " << deviceSN_ << " sensorType:" << sensorType_ << std::endl; + startStream_ = false; + pipeline_->stop(); + } + } + catch(ob::Error &e) { + std::cerr << "stopping stream failed: " << deviceSN_ << std::endl; + std::cerr << "function:" << e.getName() << "\nargs:" << e.getArgs() << "\nmessage:" << e.getMessage() << "\ntype:" << e.getExceptionType() << std::endl; + } +} + +void PipelineHolder::release() { + { + std::lock_guard lock(queueMutex_); + startStream_ = false; + } + condVar_.notify_all(); +} + +void PipelineHolder::handleStreamError(const ob::Error &e) { + std::cerr << "Function: " << e.getName() << "\nArgs: " << e.getArgs() << "\nMessage: " << e.getMessage() << "\nType: " << e.getExceptionType() << std::endl; +} + +OBFrameType PipelineHolder::mapFrameType(OBSensorType sensorType) { + switch(sensorType) { + case OB_SENSOR_COLOR: + return OB_FRAME_COLOR; + case OB_SENSOR_IR: + return OB_FRAME_IR; + case OB_SENSOR_IR_LEFT: + return OB_FRAME_IR_LEFT; + case OB_SENSOR_IR_RIGHT: + return OB_FRAME_IR_RIGHT; + case OB_SENSOR_DEPTH: + return OB_FRAME_DEPTH; + default: + return OBFrameType::OB_FRAME_UNKNOWN; + } +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/PipelineHolder.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/PipelineHolder.hpp new file mode 100644 index 0000000..655a8e3 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/PipelineHolder.hpp @@ -0,0 +1,75 @@ +#pragma once +#include +#include +#include +#include +#include +#include + + +class PipelineHolder { +public: + PipelineHolder(std::shared_ptr pipeline, OBSensorType sensorType, std::string deviceSN, int deviceIndex); + ~PipelineHolder(); + + +public: + void startStream(); + + void processFrame(std::shared_ptr frameSet); + + bool isFrameReady(); + + std::shared_ptr frontFrame(); + + void popFrame(); + + std::shared_ptr getFrame(); + + void stopStream(); + + void release(); + + void handleStreamError(const ob::Error &e); + + OBFrameType mapFrameType(OBSensorType sensorType); + + std::string getSerialNumber() { + return deviceSN_; + } + + OBSensorType getSensorType() { + return sensorType_; + } + + OBFrameType getFrameType() { + return frameType_; + } + + int getDeviceIndex(){ + return deviceIndex_; + } + + int getFrameQueueSize() { + std::lock_guard lock(queueMutex_); + return static_cast(obFrames.size()); + } +private: + bool startStream_; + + std::shared_ptr pipeline_; + OBSensorType sensorType_; + OBFrameType frameType_; + std::string deviceSN_; + int deviceIndex_; + + std::condition_variable condVar_; + std::mutex queueMutex_; + uint32_t maxFrameSize_ = 16; + + + std::queue> obFrames; + +public: + uint32_t halfTspGap; +}; diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/README.md new file mode 100644 index 0000000..68f9454 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/README.md @@ -0,0 +1,266 @@ +# C++ Sample: 3.advanced.multi_devices_sync + +## Overview + +Function description: Demonstrate multi devices synchronization operation,This sample supports network devices, USB devices, and GMSL devices (such as Gemini 335lg). + +- Network devices and USB devices must be connected to a sync hub(via the 8-pin port),please refer to the [Multi-device Sync documentation](https://www.orbbec.com/docs-general/set-up-cameras-for-external-synchronization_v1-2/). +- GMSL devices can connect via the 8-pin port or through multi-device sync via GMSL2 /FAKRA, Gemini 335lg multi device sync please refer [this document](https://www.orbbec.com/docs/gemini-335lg-hardware-synchronization/). + + +## Code overview + +### 1.Configure multi device synchronization + +```cpp + configMultiDeviceSync(); +``` + +### 2.Conduct multi device testing +```cpp + testMultiDeviceSync(); +``` +#### 2.1 Distinguishing secondary devices + +```cpp + streamDevList.clear(); + // Query the list of connected devices + auto devList = context.queryDeviceList(); + int devCount = devList->deviceCount(); + for(int i = 0; i < devCount; i++) { + streamDevList.push_back(devList->getDevice(i)); + } + + if(streamDevList.empty()) { + std::cerr << "Device list is empty. please check device connection state" << std::endl; + return -1; + } + + // traverse the device list and create the device + std::vector> primary_devices; + std::vector> secondary_devices; + for(auto dev: streamDevList) { + auto config = dev->getMultiDeviceSyncConfig(); + if(config.syncMode == OB_MULTI_DEVICE_SYNC_MODE_PRIMARY) { + primary_devices.push_back(dev); + } + else { + secondary_devices.push_back(dev); + } + } +``` + +#### 2.2 Enable secondary devices + +```cpp + std::cout << "Secondary devices start..." << std::endl; + startDeviceStreams(secondary_devices, 0); +``` + +#### 2.3 Enable Primary device + + +```cpp + std::cout << "Primary device start..." << std::endl; + startDeviceStreams(primary_devices, static_cast(secondary_devices.size())); +``` + +#### 2.4 Set software synchronization interval time + +```cpp + // Start the multi-device time synchronization function + context.enableDeviceClockSync(60000); +``` + +#### 2.5 Create a FramePairingManager object for multi-device timestamp pairing +``` cpp + auto framePairingManager = std::make_shared(); + framePairingManager->setPipelineHolderList(pipelineHolderList); +``` + +#### 2.6 Pair multiple devices based on timestamps +```cpp + std::vector, std::shared_ptr>> framePairs = framePairingManager->getFramePairs(); + if(framePairs.size() == 0) { + continue; + } +``` + +#### 7.Close data stream + +```cpp + // Stop streams and clear resources + for(auto &holder: pipelineHolderList) { + holder->stopStream(); + } + pipelineHolderList.clear(); +``` + +#### 8.Software Triggering Mode + + +Set the device synchronization mode to `OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING` after opening the stream, and the device will wait for the trigger signal (command) sent by the upper layer after opening the stream. The number of frames to be triggered for triggering mode can be configured through `framesPerTrigger`. The method for triggering images: + +```c++ +auto multiDeviceSyncConfig = dev->getMultiDeviceSyncConfig(); +if(multiDeviceSyncConfig.syncMode == OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING) +{ + dev->triggerCapture(); +} +``` + +*Press `t` in the render window to trigger a capture once.* + +## Configuration file parameter description +**Notes:"The configuration parameters for multi-device Sync may vary between different devices. Please refer to the [Multi-device Sync documentation](https://www.orbbec.com/docs-general/set-up-cameras-for-external-synchronization_v1-2/)** + +config file : MultiDeviceSyncConfig.json + +``` +{ + "version": "1.0.0", + "configTime": "2023/01/01", + "devices": [ + { + "sn": "CP2194200060", //device serial number + "syncConfig": { + "syncMode": "OB_MULTI_DEVICE_SYNC_MODE_PRIMARY", // sync mode + "depthDelayUs": 0, //Configure depth trigger delay, unit: microseconds + "colorDelayUs": 0, //Configure color trigger delay, unit: microseconds + "trigger2ImageDelayUs": 0, //Configure trigger image delay, unit: microseconds + "triggerOutEnable": true, //Configure trigger signal output enable. + "triggerOutDelayUs": 0, //Configure trigger signal output delay, unit: microsecond + "framesPerTrigger": 1 //Configure the number of frames captured by each trigger in the trigger mode + } + }, + { + "sn": "CP0Y8420004K", + "syncConfig": { + "syncMode": "OB_MULTI_DEVICE_SYNC_MODE_SECONDARY", + "depthDelayUs": 0, + "colorDelayUs": 0, + "trigger2ImageDelayUs": 0, + "triggerOutEnable": true, + "triggerOutDelayUs": 0, + "framesPerTrigger": 1 + } + } + ] +} +``` +**There are three synchronization configuration methods for network devices and USB devices.** + +- The first method is to set one device as OB_MULTI_DEVICE_SYNC_MODE_PRIMARY, and configure the other devices as OB_MULTI_DEVICE_SYNC_MODE_SECONDARY. +- The second method is to set one device as OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING, and configure the other devices as OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING,Capture images by send a software trigger command(`dev->triggerCapture()`). +- The third method is to set all devices as OB_MULTI_DEVICE_SYNC_MODE_SECONDARY, in this mode, an external trigger signal is required. + +**For GMSL devices, please refer to the following document.** + + +## GMSL Multi devices Sync + +### Method 1:Multi-device sync via 8-pin port +When using 8-pin port for multi-device synchronization, in order to ensure the quality of the synchronization signal, it is necessary to use it together with a multi-device sync hub,please refer [Multi-device Sync documentation](https://www.orbbec.com/docs-general/set-up-cameras-for-external-synchronization_v1-2/). + +via 8-pin port, GMSL multi devices sync is the same as that for USB devices, and the supported synchronization modes are also the same. + +### Method 2: Multi-device sync via GMSL2/FAKRA + +GMSL Multi devices Sync please refer [this document](https://www.orbbec.com/docs/gemini-335lg-hardware-synchronization/),There are two usage methods: + +The first is to set all devices as OB_MULTI_DEVICE_SYNC_MODE_SECONDARY mode and synchronize them through PWM triggering. + +The second is to set all devices as OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING mode and synchronize them through PWM triggering. + + +PWM triggering please refer ob_multi_devices_sync_gmsltrigger sample. + +* Notes: To make the multi devices sync sample simple and versatile, the PWM trigger has been separated into its own sample. GMSL2/FAKRA requires running two samples for testing. If you are developing your own application, you can combine these two functionalities into a single application. + + +## Run Sample + +### windows +The following demonstrates how to use the multi-device synchronization sample on Windows with the Gemini 335L. +- Double-click ob_multi_devices_sync.exe, and the following dialog will appear,Then select 0. + +![windows_sync](image/windows_sync.png) + + +0: Configure sync mode and start stream + +1: Start stream: If the parameters for multi device sync mode have been configured, you can start the stream directly. + + +- Multi-device synchronization test results are as follows +![windows sync result](image/windows_sync_result.png) + + +Observe the timestamps. As shown in the figure below, the device timestamps of the two devices are identical, indicating that the two devices are successfully synchronized. +### Linux/ARM64 + +- For USB device or Ethernet device multi-device synchronization, simply execute ob_multi_devices_sync. +``` +$ ./ob_multi_devices_sync +``` +**Notes:** + +**Multi-device sync via 8-pin port, GMSL multi devices sync is the same as that for USB devices, and the supported synchronization modes are also the same.** + + +- For GMSL device multi-device sync via GMSL2/FAKRA, run the sample according to the following steps + +**1. Open the first terminal and run the multi-devices sync sample** + +``` +$ ./ob_multi_devices_sync + +-------------------------------------------------- +Please select options: + 0 --> config devices sync mode. + 1 --> start stream +-------------------------------------------------- +Please select input: 0 +``` + + +**2. Open the second terminal and run the sample that sends PWM trigger signals with administrator privileges** + +``` +orbbec@agx:~/SensorSDK/build/install/Example/bin$ sudo ./ob_multi_devices_sync_gmsltrigger +Please select options: +------------------------------------------------------------ + 0 --> config GMSL SOC hardware trigger Source. Set trigger fps: + 1 --> start Trigger + 2 --> stop Trigger + 3 --> exit +------------------------------------------------------------ +input select item: 0 + +Enter FPS (frames per second) (for example: 3000): 3000 +Setting FPS to 3000... +Please select options: +------------------------------------------------------------ + 0 --> config GMSL SOC hardware trigger Source. Set trigger fps: + 1 --> start Trigger + 2 --> stop Trigger + 3 --> exit +------------------------------------------------------------ +input select item: 1 + +``` + +**Notes:** +- Enter FPS (frames per second) (for example: 3000): 3000(3000 indicates 30 fps) . +The differences between the two sync modes are as follows: + + - OB_MULTI_DEVICE_SYNC_MODE_SECONDARY Mode: Sets the device to secondary mode. In this mode, the PWM trigger frame rate must match the actual streaming frame rate. For example, if the streaming frame rate is 30 fps, the PWM frame rate must also be set to 30 + + - OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING mode: Sets the device to hardware triggering mode. In this mode, the PWM trigger signal must not exceed half of the streaming frame rate. For example, if the streaming frame rate is set to 30 fps, and the PWM trigger signal exceeds 15, the camera will still only capture images at 15 fps. In other words, when the streaming frame rate is 30 fps, the valid range for the PWM trigger signal is 1 to 15 fps. + + + +#### Test Results + The multi-device synchronization results of six Gemini 335Lg on AGX Orin are as follows: + +![AGX Orin sync result](image/AGX_ORIN_result.png) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/image/AGX_ORIN_result.png b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/image/AGX_ORIN_result.png new file mode 100644 index 0000000..0c7e783 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/image/AGX_ORIN_result.png differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/image/windows_sync.png b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/image/windows_sync.png new file mode 100644 index 0000000..3a3b7cc Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/image/windows_sync.png differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/image/windows_sync_result.png b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/image/windows_sync_result.png new file mode 100644 index 0000000..20c2522 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/image/windows_sync_result.png differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/ob_multi_devices_sync.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/ob_multi_devices_sync.cpp new file mode 100644 index 0000000..8c8246d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/ob_multi_devices_sync.cpp @@ -0,0 +1,449 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include +#include "PipelineHolder.hpp" +#include "FramePairingManager.hpp" +#include "utils.hpp" +#include "utils_opencv.hpp" +#include "utils/cJSON.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_DEVICE_COUNT 9 +#define CONFIG_FILE "./MultiDeviceSyncConfig.json" +#define KEY_ESC 27 + +static bool quitStreamPreview = false; + +typedef struct DeviceConfigInfo_t { + std::string deviceSN; + OBMultiDeviceSyncConfig syncConfig; +} DeviceConfigInfo; + +std::vector> streamDevList; +std::vector> configDevList; +std::vector> deviceConfigList; + +std::condition_variable waitRebootCompleteCondition; +std::mutex rebootingDevInfoListMutex; +std::vector> rebootingDevInfoList; +std::vector> pipelineHolderList; + +bool loadConfigFile(); +int configMultiDeviceSync(); +int testMultiDeviceSync(); + + +std::string OBSyncModeToString(const OBMultiDeviceSyncMode syncMode); +OBMultiDeviceSyncMode stringToOBSyncMode(const std::string &modeString); + +std::string readFileContent(const char *filePath); + +int strcmp_nocase(const char *str0, const char *str1); +bool checkDevicesWithDeviceConfigs(const std::vector> &deviceList); + +std::shared_ptr createPipelineHolder(std::shared_ptr device, OBSensorType sensorType, int deviceIndex); + +ob::Context context; + +int main(void) try { + int choice; + int exitValue = 0; + constexpr std::streamsize maxInputIgnore = 10000; + + while(true) { + std::cout << "\n--------------------------------------------------\n"; + std::cout << "Please select options: \n"; + std::cout << " 0 --> config devices sync mode. \n"; + std::cout << " 1 --> start stream \n"; + std::cout << "--------------------------------------------------\n"; + std::cout << "Please select input: "; + // std::cin >> choice; + if(!(std::cin >> choice)) { + std::cin.clear(); + std::cin.ignore(maxInputIgnore, '\n'); + std::cout << "Invalid input. Please enter a number [0~1]" << std::endl; + continue; + } + std::cout << std::endl; + + switch(choice) { + case 0: + exitValue = configMultiDeviceSync(); + if(exitValue == 0) { + std::cout << "Config MultiDeviceSync Success. \n" << std::endl; + + exitValue = testMultiDeviceSync(); + } + break; + case 1: + std::cout << "\nStart Devices video stream." << std::endl; + exitValue = testMultiDeviceSync(); + break; + default: + break; + } + + if(exitValue == 0) { + break; + } + } + return exitValue; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +int configMultiDeviceSync() { + try { + if(!loadConfigFile()) { + std::cout << "load config failed" << std::endl; + return -1; + } + + if(deviceConfigList.empty()) { + std::cout << "DeviceConfigList is empty. please check config file: " << CONFIG_FILE << std::endl; + return -1; + } + + // Query the list of connected devices + auto devList = context.queryDeviceList(); + int devCount = devList->deviceCount(); + for(int i = 0; i < devCount; i++) { + std::shared_ptr device = devList->getDevice(i); + configDevList.push_back(devList->getDevice(i)); + } + + if(configDevList.empty()) { + std::cerr << "Device list is empty. please check device connection state" << std::endl; + return -1; + } + + // write configuration to device + for(auto config: deviceConfigList) { + auto findItr = std::find_if(configDevList.begin(), configDevList.end(), [config](std::shared_ptr device) { + auto serialNumber = device->getDeviceInfo()->serialNumber(); + return strcmp_nocase(serialNumber, config->deviceSN.c_str()) == 0; + }); + if(findItr != configDevList.end()) { + auto device = (*findItr); + auto curConfig = device->getMultiDeviceSyncConfig(); + // Update the configuration items of the configuration file, and keep the original configuration for other items + curConfig.syncMode = config->syncConfig.syncMode; + curConfig.depthDelayUs = config->syncConfig.depthDelayUs; + curConfig.colorDelayUs = config->syncConfig.colorDelayUs; + curConfig.trigger2ImageDelayUs = config->syncConfig.trigger2ImageDelayUs; + curConfig.triggerOutEnable = config->syncConfig.triggerOutEnable; + curConfig.triggerOutDelayUs = config->syncConfig.triggerOutDelayUs; + curConfig.framesPerTrigger = config->syncConfig.framesPerTrigger; + std::cout << "-Config Device syncMode:" << curConfig.syncMode << ", syncModeStr:" << OBSyncModeToString(curConfig.syncMode) << std::endl; + device->setMultiDeviceSyncConfig(curConfig); + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + return 0; + } + catch(ob::Error &e) { + std::cerr << "configMultiDeviceSync failed! \n"; + std::cerr << "function:" << e.getName() << "\nargs:" << e.getArgs() << "\nmessage:" << e.getMessage() << "\ntype:" << e.getExceptionType() << std::endl; + return -1; + } +} + +void startDeviceStreams(const std::vector> &devices, int startIndex) { + std::vector sensorTypes = { OB_SENSOR_DEPTH, OB_SENSOR_COLOR }; + for(auto &dev: devices) { + for(auto sensorType: sensorTypes) { + auto holder = createPipelineHolder(dev, sensorType, startIndex); + pipelineHolderList.push_back(holder); + holder->startStream(); + } + startIndex++; + } + quitStreamPreview = false; +} + +// key press event processing +void handleKeyPress(ob_smpl::CVWindow &win, int key) { + // Get the key value + if(key == KEY_ESC) { + if(!quitStreamPreview) { + win.setShowInfo(false); + win.setShowSyncTimeInfo(false); + quitStreamPreview = true; + win.close(); + win.destroyWindow(); + std::cout << "press ESC quitStreamPreview" << std::endl; + } + } + else if(key == 'S' || key == 's') { + std::cout << "syncDevicesTime..." << std::endl; + context.enableDeviceClockSync(60000); // Manual update synchronization + } + else if(key == 'T' || key == 't') { + // software trigger + std::cout << "check software trigger mode" << std::endl; + for(auto &dev: streamDevList) { + auto multiDeviceSyncConfig = dev->getMultiDeviceSyncConfig(); + if(multiDeviceSyncConfig.syncMode == OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING) { + std::cout << "software trigger..." << std::endl; + dev->triggerCapture(); + } + } + } +} + +int testMultiDeviceSync() { + try { + streamDevList.clear(); + // Query the list of connected devices + auto devList = context.queryDeviceList(); + int devCount = devList->deviceCount(); + for(int i = 0; i < devCount; i++) { + streamDevList.push_back(devList->getDevice(i)); + } + + if(streamDevList.empty()) { + std::cerr << "Device list is empty. please check device connection state" << std::endl; + return -1; + } + + // traverse the device list and create the device + std::vector> primary_devices; + std::vector> secondary_devices; + for(auto dev: streamDevList) { + auto config = dev->getMultiDeviceSyncConfig(); + if(config.syncMode == OB_MULTI_DEVICE_SYNC_MODE_PRIMARY) { + primary_devices.push_back(dev); + } + else { + secondary_devices.push_back(dev); + } + } + + std::cout << "Secondary devices start..." << std::endl; + startDeviceStreams(secondary_devices, 0); + + // Delay and wait for 5s to ensure that the initialization of the slave device is completed + // std::this_thread::sleep_for(std::chrono::milliseconds(5000)); + + if(primary_devices.empty()) { + std::cerr << "WARNING primary_devices is empty!!!" << std::endl; + } + else { + std::cout << "Primary device start..." << std::endl; + startDeviceStreams(primary_devices, static_cast(secondary_devices.size())); + } + + // Start the multi-device time synchronization function + context.enableDeviceClockSync(60000); // update and sync every minitor + + auto framePairingManager = std::make_shared(); + framePairingManager->setPipelineHolderList(pipelineHolderList); + + // Create a window for rendering and set the resolution of the window + ob_smpl::CVWindow win("MultiDeviceSyncViewer", 1600, 900, ob_smpl::ARRANGE_GRID); + + // set key prompt + win.setKeyPrompt("'S': syncDevicesTime, 'T': software trigger"); + // set the callback function for the window to handle key press events + win.setKeyPressedCallback([&](int key) { handleKeyPress(win, key); }); + + win.setShowInfo(true); + win.setShowSyncTimeInfo(true); + while(win.run() && !quitStreamPreview) { + if(quitStreamPreview) { + break; + } + + std::vector, std::shared_ptr>> framePairs = framePairingManager->getFramePairs(); + if(framePairs.size() == 0) { + continue; + } + + auto groudID = 0; + for(const auto &pair: framePairs) { + groudID++; + win.pushFramesToView({ pair.first, pair.second }, groudID); + } + } + + framePairingManager->release(); + + // Stop streams and clear resources + for(auto &holder: pipelineHolderList) { + holder->stopStream(); + } + pipelineHolderList.clear(); + + // Release resource + streamDevList.clear(); + configDevList.clear(); + deviceConfigList.clear(); + return 0; + } + catch(ob::Error &e) { + std::cerr << "function:" << e.getName() << "\nargs:" << e.getArgs() << "\nmessage:" << e.getMessage() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); + return -1; + } +} + +std::shared_ptr createPipelineHolder(std::shared_ptr device, OBSensorType sensorType, int deviceIndex) { + auto pipeline = std::make_shared(device); + auto holder = std::make_shared(pipeline, sensorType, device->getDeviceInfo()->serialNumber(), deviceIndex); + return holder; +} + +std::string readFileContent(const char *filePath) { + std::ostringstream oss; + std::ifstream file(filePath, std::fstream::in); + if(!file.is_open()) { + std::cerr << "Failed to open file: " << filePath << std::endl; + return ""; + } + oss << file.rdbuf(); + file.close(); + return oss.str(); +} + +bool loadConfigFile() { + int deviceCount = 0; + std::shared_ptr devConfigInfo = nullptr; + cJSON *deviceElem = nullptr; + + auto content = readFileContent(CONFIG_FILE); + if(content.empty()) { + std::cerr << "load config file failed." << std::endl; + return false; + } + + cJSON *rootElem = cJSON_Parse(content.c_str()); + if(rootElem == nullptr) { + const char *errMsg = cJSON_GetErrorPtr(); + std::cout << std::string(errMsg) << std::endl; + cJSON_Delete(rootElem); + return true; + } + + cJSON *devicesElem = cJSON_GetObjectItem(rootElem, "devices"); + cJSON_ArrayForEach(deviceElem, devicesElem) { + devConfigInfo = std::make_shared(); + memset(&devConfigInfo->syncConfig, 0, sizeof(devConfigInfo->syncConfig)); + devConfigInfo->syncConfig.syncMode = OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN; + + cJSON *snElem = cJSON_GetObjectItem(deviceElem, "sn"); + if(cJSON_IsString(snElem) && snElem->valuestring != nullptr) { + devConfigInfo->deviceSN = std::string(snElem->valuestring); + } + cJSON *deviceConfigElem = cJSON_GetObjectItem(deviceElem, "syncConfig"); + if(cJSON_IsObject(deviceConfigElem)) { + cJSON *numberElem = nullptr; + cJSON *strElem = nullptr; + cJSON *bElem = nullptr; + strElem = cJSON_GetObjectItemCaseSensitive(deviceConfigElem, "syncMode"); + if(cJSON_IsString(strElem) && strElem->valuestring != nullptr) { + devConfigInfo->syncConfig.syncMode = stringToOBSyncMode(strElem->valuestring); + std::cout << "config[" << (deviceCount++) << "]: SN=" << std::string(devConfigInfo->deviceSN) << ", mode=" << strElem->valuestring << std::endl; + } + numberElem = cJSON_GetObjectItemCaseSensitive(deviceConfigElem, "depthDelayUs"); + if(cJSON_IsNumber(numberElem)) { + devConfigInfo->syncConfig.depthDelayUs = numberElem->valueint; + } + numberElem = cJSON_GetObjectItemCaseSensitive(deviceConfigElem, "colorDelayUs"); + if(cJSON_IsNumber(numberElem)) { + devConfigInfo->syncConfig.colorDelayUs = numberElem->valueint; + } + numberElem = cJSON_GetObjectItemCaseSensitive(deviceConfigElem, "trigger2ImageDelayUs"); + if(cJSON_IsNumber(numberElem)) { + devConfigInfo->syncConfig.trigger2ImageDelayUs = numberElem->valueint; + } + numberElem = cJSON_GetObjectItemCaseSensitive(deviceConfigElem, "triggerOutDelayUs"); + if(cJSON_IsNumber(numberElem)) { + devConfigInfo->syncConfig.triggerOutDelayUs = numberElem->valueint; + } + bElem = cJSON_GetObjectItemCaseSensitive(deviceConfigElem, "triggerOutEnable"); + if(cJSON_IsBool(bElem)) { + devConfigInfo->syncConfig.triggerOutEnable = (bool)bElem->valueint; + } + bElem = cJSON_GetObjectItemCaseSensitive(deviceConfigElem, "framesPerTrigger"); + if(cJSON_IsNumber(bElem)) { + devConfigInfo->syncConfig.framesPerTrigger = bElem->valueint; + } + } + + if(OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN != devConfigInfo->syncConfig.syncMode) { + deviceConfigList.push_back(devConfigInfo); + } + else { + std::cerr << "Invalid sync mode of deviceSN: " << devConfigInfo->deviceSN << std::endl; + } + devConfigInfo = nullptr; + } + cJSON_Delete(rootElem); + return true; +} + +OBMultiDeviceSyncMode stringToOBSyncMode(const std::string &modeString) { + static const std::unordered_map syncModeMap = { + { "OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN", OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN }, + { "OB_MULTI_DEVICE_SYNC_MODE_STANDALONE", OB_MULTI_DEVICE_SYNC_MODE_STANDALONE }, + { "OB_MULTI_DEVICE_SYNC_MODE_PRIMARY", OB_MULTI_DEVICE_SYNC_MODE_PRIMARY }, + { "OB_MULTI_DEVICE_SYNC_MODE_SECONDARY", OB_MULTI_DEVICE_SYNC_MODE_SECONDARY }, + { "OB_MULTI_DEVICE_SYNC_MODE_SECONDARY_SYNCED", OB_MULTI_DEVICE_SYNC_MODE_SECONDARY_SYNCED }, + { "OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING", OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING }, + { "OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING", OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING } + }; + auto it = syncModeMap.find(modeString); + if(it != syncModeMap.end()) { + return it->second; + } + // Constructing exception messages with stringstream + std::stringstream ss; + ss << "Unrecognized sync mode: " << modeString; + throw std::invalid_argument(ss.str()); +} + +std::string OBSyncModeToString(const OBMultiDeviceSyncMode syncMode) { + static const std::unordered_map modeToStringMap = { + { OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN, "OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN" }, + { OB_MULTI_DEVICE_SYNC_MODE_STANDALONE, "OB_MULTI_DEVICE_SYNC_MODE_STANDALONE" }, + { OB_MULTI_DEVICE_SYNC_MODE_PRIMARY, "OB_MULTI_DEVICE_SYNC_MODE_PRIMARY" }, + { OB_MULTI_DEVICE_SYNC_MODE_SECONDARY, "OB_MULTI_DEVICE_SYNC_MODE_SECONDARY" }, + { OB_MULTI_DEVICE_SYNC_MODE_SECONDARY_SYNCED, "OB_MULTI_DEVICE_SYNC_MODE_SECONDARY_SYNCED" }, + { OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING, "OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING" }, + { OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING, "OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING" } + }; + + auto it = modeToStringMap.find(syncMode); + if(it != modeToStringMap.end()) { + return it->second; + } + std::stringstream ss; + ss << "Unmapped sync mode value: " << static_cast(syncMode) << ". Please check the sync mode value."; + throw std::invalid_argument(ss.str()); +} + +int strcmp_nocase(const char *str0, const char *str1) { +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + return _strcmpi(str0, str1); +#else + return strcasecmp(str0, str1); +#endif +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/utils/cJSON.c b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/utils/cJSON.c new file mode 100644 index 0000000..4a338db --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/utils/cJSON.c @@ -0,0 +1,2667 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + 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 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. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning(push) +/* disable warning about single line comments in system headers */ +#pragma warning(disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0 / 0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) { + return (const char *)(global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON *const item) { + if(!cJSON_IsString(item)) { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON *const item) { + if(!cJSON_IsNumber(item)) { + return (double)NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if(CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15) +#error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char *) cJSON_Version(void) { + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) { + if((string1 == NULL) || (string2 == NULL)) { + return 1; + } + + if(string1 == string2) { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) { + if(*string1 == '\0') { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks { + void *(CJSON_CDECL *allocate)(size_t size); + void(CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void *CJSON_CDECL internal_malloc(size_t size) { + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) { + free(pointer); +} +static void *CJSON_CDECL internal_realloc(void *pointer, size_t size) { + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char *cJSON_strdup(const unsigned char *string, const internal_hooks *const hooks) { + size_t length = 0; + unsigned char *copy = NULL; + + if(string == NULL) { + return NULL; + } + + length = strlen((const char *)string) + sizeof(""); + copy = (unsigned char *)hooks->allocate(length); + if(copy == NULL) { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks *hooks) { + if(hooks == NULL) { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if(hooks->malloc_fn != NULL) { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if(hooks->free_fn != NULL) { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks *const hooks) { + cJSON *node = (cJSON *)hooks->allocate(sizeof(cJSON)); + if(node) { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) { + cJSON *next = NULL; + while(item != NULL) { + next = item->next; + if(!(item->type & cJSON_IsReference) && (item->child != NULL)) { + cJSON_Delete(item->child); + } + if(!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) { + global_hooks.deallocate(item->valuestring); + } + if(!(item->type & cJSON_StringIsConst) && (item->string != NULL)) { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) { +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char)lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct { + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON *const item, parse_buffer *const input_buffer) { + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if((input_buffer == NULL) || (input_buffer->content == NULL)) { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for(i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) { + switch(buffer_at_offset(input_buffer)[i]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char *)number_c_string, (char **)&after_end); + if(number_c_string == after_end) { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if(number >= INT_MAX) { + item->valueint = INT_MAX; + } + else if(number <= (double)INT_MIN) { + item->valueint = INT_MIN; + } + else { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) { + if(number >= INT_MAX) { + object->valueint = INT_MAX; + } + else if(number <= (double)INT_MIN) { + object->valueint = INT_MIN; + } + else { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +CJSON_PUBLIC(char *) cJSON_SetValuestring(cJSON *object, const char *valuestring) { + char *copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if(!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) { + return NULL; + } + if(strlen(valuestring) <= strlen(object->valuestring)) { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char *)cJSON_strdup((const unsigned char *)valuestring, &global_hooks); + if(copy == NULL) { + return NULL; + } + if(object->valuestring != NULL) { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct { + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char *ensure(printbuffer *const p, size_t needed) { + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if((p == NULL) || (p->buffer == NULL)) { + return NULL; + } + + if((p->length > 0) && (p->offset >= p->length)) { + /* make sure that offset is valid */ + return NULL; + } + + if(needed > INT_MAX) { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if(needed <= p->length) { + return p->buffer + p->offset; + } + + if(p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if(needed > (INT_MAX / 2)) { + /* overflow of int, use INT_MAX if possible */ + if(needed <= INT_MAX) { + newsize = INT_MAX; + } + else { + return NULL; + } + } + else { + newsize = needed * 2; + } + + if(p->hooks.reallocate != NULL) { + /* reallocate with realloc if available */ + newbuffer = (unsigned char *)p->hooks.reallocate(p->buffer, newsize); + if(newbuffer == NULL) { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else { + /* otherwise reallocate manually */ + newbuffer = (unsigned char *)p->hooks.allocate(newsize); + if(!newbuffer) { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer *const buffer) { + const unsigned char *buffer_pointer = NULL; + if((buffer == NULL) || (buffer->buffer == NULL)) { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char *)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) { + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON *const item, printbuffer *const output_buffer) { + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = { 0 }; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if(output_buffer == NULL) { + return false; + } + + /* This checks for NaN and Infinity */ + if(isnan(d) || isinf(d)) { + length = sprintf((char *)number_buffer, "null"); + } + else { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char *)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if((sscanf((char *)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char *)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if(output_pointer == NULL) { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for(i = 0; i < ((size_t)length); i++) { + if(number_buffer[i] == decimal_point) { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char *const input) { + unsigned int h = 0; + size_t i = 0; + + for(i = 0; i < 4; i++) { + /* parse digit */ + if((input[i] >= '0') && (input[i] <= '9')) { + h += (unsigned int)input[i] - '0'; + } + else if((input[i] >= 'A') && (input[i] <= 'F')) { + h += (unsigned int)10 + input[i] - 'A'; + } + else if((input[i] >= 'a') && (input[i] <= 'f')) { + h += (unsigned int)10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if(i < 3) { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char *const input_pointer, const unsigned char *const input_end, unsigned char **output_pointer) { + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if((input_end - first_sequence) < 6) { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if(((first_code >= 0xDC00) && (first_code <= 0xDFFF))) { + goto fail; + } + + /* UTF16 surrogate pair */ + if((first_code >= 0xD800) && (first_code <= 0xDBFF)) { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if((input_end - second_sequence) < 6) { + /* input ends unexpectedly */ + goto fail; + } + + if((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if((second_code < 0xDC00) || (second_code > 0xDFFF)) { + /* invalid second half of the surrogate pair */ + goto fail; + } + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if(codepoint < 0x80) { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if(codepoint < 0x800) { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if(codepoint < 0x10000) { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if(codepoint <= 0x10FFFF) { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for(utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if(utf8_length > 1) { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON *const item, parse_buffer *const input_buffer) { + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if(buffer_at_offset(input_buffer)[0] != '\"') { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while(((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) { + /* is escape sequence */ + if(input_end[0] == '\\') { + if((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if(((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t)(input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char *)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if(output == NULL) { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while(input_pointer < input_end) { + if(*input_pointer != '\\') { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else { + unsigned char sequence_length = 2; + if((input_end - input_pointer) < 1) { + goto fail; + } + + switch(input_pointer[1]) { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if(sequence_length == 0) { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char *)output; + + input_buffer->offset = (size_t)(input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if(output != NULL) { + input_buffer->hooks.deallocate(output); + } + + if(input_pointer != NULL) { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char *const input, printbuffer *const output_buffer) { + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if(output_buffer == NULL) { + return false; + } + + /* empty string */ + if(input == NULL) { + output = ensure(output_buffer, sizeof("\"\"")); + if(output == NULL) { + return false; + } + strcpy((char *)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for(input_pointer = input; *input_pointer; input_pointer++) { + switch(*input_pointer) { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if(*input_pointer < 32) { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if(output == NULL) { + return false; + } + + /* no characters have to be escaped */ + if(escape_characters == 0) { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for(input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) { + if((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch(*input_pointer) { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char *)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON *const item, printbuffer *const p) { + return print_string_ptr((unsigned char *)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON *const item, parse_buffer *const input_buffer); +static cJSON_bool print_value(const cJSON *const item, printbuffer *const output_buffer); +static cJSON_bool parse_array(cJSON *const item, parse_buffer *const input_buffer); +static cJSON_bool print_array(const cJSON *const item, printbuffer *const output_buffer); +static cJSON_bool parse_object(cJSON *const item, parse_buffer *const input_buffer); +static cJSON_bool print_object(const cJSON *const item, printbuffer *const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer *const buffer) { + if((buffer == NULL) || (buffer->content == NULL)) { + return NULL; + } + + if(cannot_access_at_index(buffer, 0)) { + return buffer; + } + + while(can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) { + buffer->offset++; + } + + if(buffer->offset == buffer->length) { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer *const buffer) { + if((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) { + return NULL; + } + + if(can_access_at_index(buffer, 4) && (strncmp((const char *)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) { + size_t buffer_length; + + if(NULL == value) { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) { + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if(value == NULL || 0 == buffer_length) { + goto fail; + } + + buffer.content = (const unsigned char *)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if(item == NULL) /* memory fail */ + { + goto fail; + } + + if(!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if(require_null_terminated) { + buffer_skip_whitespace(&buffer); + if((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') { + goto fail; + } + } + if(return_parse_end) { + *return_parse_end = (const char *)buffer_at_offset(&buffer); + } + + return item; + +fail: + if(item != NULL) { + cJSON_Delete(item); + } + + if(value != NULL) { + error local_error; + local_error.json = (const unsigned char *)value; + local_error.position = 0; + + if(buffer.offset < buffer.length) { + local_error.position = buffer.offset; + } + else if(buffer.length > 0) { + local_error.position = buffer.length - 1; + } + + if(return_parse_end != NULL) { + *return_parse_end = (const char *)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) { + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) { + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON *const item, cJSON_bool format, const internal_hooks *const hooks) { + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char *)hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if(buffer->buffer == NULL) { + goto fail; + } + + /* print the value */ + if(!print_value(item, buffer)) { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if(hooks->reallocate != NULL) { + printed = (unsigned char *)hooks->reallocate(buffer->buffer, buffer->offset + 1); + if(printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char *)hooks->allocate(buffer->offset + 1); + if(printed == NULL) { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if(buffer->buffer != NULL) { + hooks->deallocate(buffer->buffer); + } + + if(printed != NULL) { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) { + return (char *)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) { + return (char *)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) { + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if(prebuffer < 0) { + return NULL; + } + + p.buffer = (unsigned char *)global_hooks.allocate((size_t)prebuffer); + if(!p.buffer) { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if(!print_value(item, &p)) { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char *)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) { + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if((length < 0) || (buffer == NULL)) { + return false; + } + + p.buffer = (unsigned char *)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON *const item, parse_buffer *const input_buffer) { + if((input_buffer == NULL) || (input_buffer->content == NULL)) { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if(can_read(input_buffer, 4) && (strncmp((const char *)buffer_at_offset(input_buffer), "null", 4) == 0)) { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if(can_read(input_buffer, 5) && (strncmp((const char *)buffer_at_offset(input_buffer), "false", 5) == 0)) { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if(can_read(input_buffer, 4) && (strncmp((const char *)buffer_at_offset(input_buffer), "true", 4) == 0)) { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if(can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) { + return parse_string(item, input_buffer); + } + /* number */ + if(can_access_at_index(input_buffer, 0) + && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) { + return parse_number(item, input_buffer); + } + /* array */ + if(can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) { + return parse_array(item, input_buffer); + } + /* object */ + if(can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON *const item, printbuffer *const output_buffer) { + unsigned char *output = NULL; + + if((item == NULL) || (output_buffer == NULL)) { + return false; + } + + switch((item->type) & 0xFF) { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if(output == NULL) { + return false; + } + strcpy((char *)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if(output == NULL) { + return false; + } + strcpy((char *)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if(output == NULL) { + return false; + } + strcpy((char *)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: { + size_t raw_length = 0; + if(item->valuestring == NULL) { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if(output == NULL) { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON *const item, parse_buffer *const input_buffer) { + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if(input_buffer->depth >= CJSON_NESTING_LIMIT) { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if(buffer_at_offset(input_buffer)[0] != '[') { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if(can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if(cannot_access_at_index(input_buffer, 0)) { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if(new_item == NULL) { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if(head == NULL) { + /* start the linked list */ + current_item = head = new_item; + } + else { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if(!parse_value(current_item, input_buffer)) { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } while(can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if(cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if(head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if(head != NULL) { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON *const item, printbuffer *const output_buffer) { + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if(output_buffer == NULL) { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if(output_pointer == NULL) { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while(current_element != NULL) { + if(!print_value(current_element, output_buffer)) { + return false; + } + update_offset(output_buffer); + if(current_element->next) { + length = (size_t)(output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if(output_pointer == NULL) { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if(output_pointer == NULL) { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON *const item, parse_buffer *const input_buffer) { + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if(input_buffer->depth >= CJSON_NESTING_LIMIT) { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if(cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if(can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if(cannot_access_at_index(input_buffer, 0)) { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if(new_item == NULL) { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if(head == NULL) { + /* start the linked list */ + current_item = head = new_item; + } + else { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if(!parse_string(current_item, input_buffer)) { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if(cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if(!parse_value(current_item, input_buffer)) { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } while(can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if(cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if(head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if(head != NULL) { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON *const item, printbuffer *const output_buffer) { + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if(output_buffer == NULL) { + return false; + } + + /* Compose the output: */ + length = (size_t)(output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if(output_pointer == NULL) { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if(output_buffer->format) { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while(current_item) { + if(output_buffer->format) { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if(output_pointer == NULL) { + return false; + } + for(i = 0; i < output_buffer->depth; i++) { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if(!print_string_ptr((unsigned char *)current_item->string, output_buffer)) { + return false; + } + update_offset(output_buffer); + + length = (size_t)(output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if(output_pointer == NULL) { + return false; + } + *output_pointer++ = ':'; + if(output_buffer->format) { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if(!print_value(current_item, output_buffer)) { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if(output_pointer == NULL) { + return false; + } + if(current_item->next) { + *output_pointer++ = ','; + } + + if(output_buffer->format) { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if(output_pointer == NULL) { + return false; + } + if(output_buffer->format) { + size_t i; + for(i = 0; i < (output_buffer->depth - 1); i++) { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) { + cJSON *child = NULL; + size_t size = 0; + + if(array == NULL) { + return 0; + } + + child = array->child; + + while(child != NULL) { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON *get_array_item(const cJSON *array, size_t index) { + cJSON *current_child = NULL; + + if(array == NULL) { + return NULL; + } + + current_child = array->child; + while((current_child != NULL) && (index > 0)) { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) { + if(index < 0) { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON *const object, const char *const name, const cJSON_bool case_sensitive) { + cJSON *current_element = NULL; + + if((object == NULL) || (name == NULL)) { + return NULL; + } + + current_element = object->child; + if(case_sensitive) { + while((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) { + current_element = current_element->next; + } + } + else { + while((current_element != NULL) && (case_insensitive_strcmp((const unsigned char *)name, (const unsigned char *)(current_element->string)) != 0)) { + current_element = current_element->next; + } + } + + if((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON *const object, const char *const string) { + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON *const object, const char *const string) { + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) { + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) { + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks *const hooks) { + cJSON *reference = NULL; + if(item == NULL) { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if(reference == NULL) { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) { + cJSON *child = NULL; + + if((item == NULL) || (array == NULL) || (array == item)) { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if(child == NULL) { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else { + /* append to the end */ + if(child->prev) { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) { + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void *cast_away_const(const void *string) { + return (void *)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic pop +#endif + +static cJSON_bool add_item_to_object(cJSON *const object, const char *const string, cJSON *const item, const internal_hooks *const hooks, + const cJSON_bool constant_key) { + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) { + return false; + } + + if(constant_key) { + new_key = (char *)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else { + new_key = (char *)cJSON_strdup((const unsigned char *)string, hooks); + if(new_key == NULL) { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if(!(item->type & cJSON_StringIsConst) && (item->string != NULL)) { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) { + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) { + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) { + if(array == NULL) { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) { + if((object == NULL) || (string == NULL)) { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_AddNullToObject(cJSON *const object, const char *const name) { + cJSON *null = cJSON_CreateNull(); + if(add_item_to_object(object, name, null, &global_hooks, false)) { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_AddTrueToObject(cJSON *const object, const char *const name) { + cJSON *true_item = cJSON_CreateTrue(); + if(add_item_to_object(object, name, true_item, &global_hooks, false)) { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_AddFalseToObject(cJSON *const object, const char *const name) { + cJSON *false_item = cJSON_CreateFalse(); + if(add_item_to_object(object, name, false_item, &global_hooks, false)) { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_AddBoolToObject(cJSON *const object, const char *const name, const cJSON_bool boolean) { + cJSON *bool_item = cJSON_CreateBool(boolean); + if(add_item_to_object(object, name, bool_item, &global_hooks, false)) { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_AddNumberToObject(cJSON *const object, const char *const name, const double number) { + cJSON *number_item = cJSON_CreateNumber(number); + if(add_item_to_object(object, name, number_item, &global_hooks, false)) { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_AddStringToObject(cJSON *const object, const char *const name, const char *const string) { + cJSON *string_item = cJSON_CreateString(string); + if(add_item_to_object(object, name, string_item, &global_hooks, false)) { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_AddRawToObject(cJSON *const object, const char *const name, const char *const raw) { + cJSON *raw_item = cJSON_CreateRaw(raw); + if(add_item_to_object(object, name, raw_item, &global_hooks, false)) { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_AddObjectToObject(cJSON *const object, const char *const name) { + cJSON *object_item = cJSON_CreateObject(); + if(add_item_to_object(object, name, object_item, &global_hooks, false)) { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_AddArrayToObject(cJSON *const object, const char *const name) { + cJSON *array = cJSON_CreateArray(); + if(add_item_to_object(object, name, array, &global_hooks, false)) { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON *const item) { + if((parent == NULL) || (item == NULL)) { + return NULL; + } + + if(item != parent->child) { + /* not the first element */ + item->prev->next = item->next; + } + if(item->next != NULL) { + /* not the last element */ + item->next->prev = item->prev; + } + + if(item == parent->child) { + /* first element */ + parent->child = item->next; + } + else if(item->next == NULL) { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) { + if(which < 0) { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) { + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) { + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) { + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) { + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) { + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) { + cJSON *after_inserted = NULL; + + if(which < 0) { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if(after_inserted == NULL) { + return add_item_to_array(array, newitem); + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if(after_inserted == array->child) { + array->child = newitem; + } + else { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON *const parent, cJSON *const item, cJSON *replacement) { + if((parent == NULL) || (replacement == NULL) || (item == NULL)) { + return false; + } + + if(replacement == item) { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if(replacement->next != NULL) { + replacement->next->prev = replacement; + } + if(parent->child == item) { + if(parent->child->prev == parent->child) { + replacement->prev = replacement; + } + parent->child = replacement; + } + else { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if(replacement->prev != NULL) { + replacement->prev->next = replacement; + } + if(replacement->next == NULL) { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) { + if(which < 0) { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) { + if((replacement == NULL) || (string == NULL)) { + return false; + } + + /* replace the name in the replacement */ + if(!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) { + cJSON_free(replacement->string); + } + replacement->string = (char *)cJSON_strdup((const unsigned char *)string, &global_hooks); + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) { + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) { + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if(num >= INT_MAX) { + item->valueint = INT_MAX; + } + else if(num <= (double)INT_MIN) { + item->valueint = INT_MIN; + } + else { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) { + item->type = cJSON_String; + item->valuestring = (char *)cJSON_strdup((const unsigned char *)string, &global_hooks); + if(!item->valuestring) { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item != NULL) { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char *)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON *)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON *)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) { + item->type = cJSON_Raw; + item->valuestring = (char *)cJSON_strdup((const unsigned char *)raw, &global_hooks); + if(!item->valuestring) { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) { + item->type = cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) { + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) { + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if((count < 0) || (numbers == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) { + n = cJSON_CreateNumber(numbers[i]); + if(!n) { + cJSON_Delete(a); + return NULL; + } + if(!i) { + a->child = n; + } + else { + suffix_object(p, n); + } + p = n; + } + + if(a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) { + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if((count < 0) || (numbers == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) { + cJSON_Delete(a); + return NULL; + } + if(!i) { + a->child = n; + } + else { + suffix_object(p, n); + } + p = n; + } + + if(a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) { + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if((count < 0) || (numbers == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) { + n = cJSON_CreateNumber(numbers[i]); + if(!n) { + cJSON_Delete(a); + return NULL; + } + if(!i) { + a->child = n; + } + else { + suffix_object(p, n); + } + p = n; + } + + if(a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) { + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if((count < 0) || (strings == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) { + n = cJSON_CreateString(strings[i]); + if(!n) { + cJSON_Delete(a); + return NULL; + } + if(!i) { + a->child = n; + } + else { + suffix_object(p, n); + } + p = n; + } + + if(a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) { + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if(!item) { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if(!newitem) { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if(item->valuestring) { + newitem->valuestring = (char *)cJSON_strdup((unsigned char *)item->valuestring, &global_hooks); + if(!newitem->valuestring) { + goto fail; + } + } + if(item->string) { + newitem->string = (item->type & cJSON_StringIsConst) ? item->string : (char *)cJSON_strdup((unsigned char *)item->string, &global_hooks); + if(!newitem->string) { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if(!recurse) { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while(child != NULL) { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if(!newchild) { + goto fail; + } + if(next != NULL) { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if(newitem && newitem->child) { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if(newitem != NULL) { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) { + *input += static_strlen("//"); + + for(; (*input)[0] != '\0'; ++(*input)) { + if((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) { + *input += static_strlen("/*"); + + for(; (*input)[0] != '\0'; ++(*input)) { + if(((*input)[0] == '*') && ((*input)[1] == '/')) { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + for(; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } + else if(((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) { + char *into = json; + + if(json == NULL) { + return; + } + + while(json[0] != '\0') { + switch(json[0]) { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if(json[1] == '/') { + skip_oneline_comment(&json); + } + else if(json[1] == '*') { + skip_multiline_comment(&json); + } + else { + json++; + } + break; + + case '\"': + minify_string(&json, (char **)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON *const item) { + if(item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON *const a, const cJSON *const b, const cJSON_bool case_sensitive) { + if((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) { + return false; + } + + /* check if type is valid */ + switch(a->type & 0xFF) { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if(a == b) { + return true; + } + + switch(a->type & 0xFF) { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if(compare_double(a->valuedouble, b->valuedouble)) { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if((a->valuestring == NULL) || (b->valuestring == NULL)) { + return false; + } + if(strcmp(a->valuestring, b->valuestring) == 0) { + return true; + } + + return false; + + case cJSON_Array: { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for(; (a_element != NULL) && (b_element != NULL);) { + if(!cJSON_Compare(a_element, b_element, case_sensitive)) { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if(a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if(b_element == NULL) { + return false; + } + + if(!cJSON_Compare(a_element, b_element, case_sensitive)) { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) { + a_element = get_object_item(a, b_element->string, case_sensitive); + if(a_element == NULL) { + return false; + } + + if(!cJSON_Compare(b_element, a_element, case_sensitive)) { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) { + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) { + global_hooks.deallocate(object); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/utils/cJSON.h b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/utils/cJSON.h new file mode 100644 index 0000000..f4a6db5 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync/utils/cJSON.h @@ -0,0 +1,298 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + 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 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. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default +calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if(defined(__GNUC__) || defined(__SUNPRO_CC) || defined(__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 15 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON { + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks { + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions + * directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void(CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char *) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks *hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib + * free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. + */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, + * =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON *const object, const char *const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON *const object, const char *const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when + * cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON *const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON *const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON *const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your + * existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON *const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON *const parent, cJSON *const item, cJSON *replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON *const a, const cJSON *const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON *) cJSON_AddNullToObject(cJSON *const object, const char *const name); +CJSON_PUBLIC(cJSON *) cJSON_AddTrueToObject(cJSON *const object, const char *const name); +CJSON_PUBLIC(cJSON *) cJSON_AddFalseToObject(cJSON *const object, const char *const name); +CJSON_PUBLIC(cJSON *) cJSON_AddBoolToObject(cJSON *const object, const char *const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_AddNumberToObject(cJSON *const object, const char *const name, const double number); +CJSON_PUBLIC(cJSON *) cJSON_AddStringToObject(cJSON *const object, const char *const name, const char *const string); +CJSON_PUBLIC(cJSON *) cJSON_AddRawToObject(cJSON *const object, const char *const name, const char *const raw); +CJSON_PUBLIC(cJSON *) cJSON_AddObjectToObject(cJSON *const object, const char *const name); +CJSON_PUBLIC(cJSON *) cJSON_AddArrayToObject(cJSON *const object, const char *const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char *) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync_gmsltrigger/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync_gmsltrigger/CMakeLists.txt new file mode 100644 index 0000000..81662b2 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync_gmsltrigger/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_multi_devices_sync_gmsltrigger) + +add_executable(${PROJECT_NAME} ob_multi_devices_sync_gmsltrigger.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync_gmsltrigger/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync_gmsltrigger/README.md new file mode 100644 index 0000000..5728886 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync_gmsltrigger/README.md @@ -0,0 +1,114 @@ +# C++ Sample: 3.advanced.multi_devices_sync_gmsltrigger + +## Overview + +GMSL Multi-device Hardware Trigger Configuration + +support Connection of GMSL device (Nvidia Xavier/Orin platform) + +### Knowledge + +Send a hardware synchronization signal with a set frequency to all connected GMSL devices for multi-machine hardware synchronization through the /dev/camsync device node. + +## Code overview + +1.open /dev/camsync device. + +2.set trigger frequency + +```cpp + int startTrigger(uint16_t triggerFps) { + if(!isDeviceOpen) { + if(openDevice() < 0) { + error("open device Failed!"); + return -1; + } + } + cs_param_t wt_param = { WRITE_MODE, triggerFps }; + int ret = writeTriggerParam(wt_param); + if(ret < 0) { + error("write trigger parameter Failed!"); + return ret; + } + info("write param: ", wt_param); + + cs_param_t rd_param = { READ_MODE, 0 }; + ret = readTriggerParam(rd_param); + if(ret < 0) { + error("read trigger parameter Failed!"); + } + info("read param: ", rd_param); + + return ret; + } +``` + +## Run Sample + +### 1.**Setup of Trigger Source and Device Node Permissions** + +``` +sudo chmod 777 /dev/camsync +``` + +### 2.**Running Configuration and Triggering Program** + +The program is located in the Example/bin directory of the orbbecsdk. + +Sample Configuration: 30FPS and Trigger Source Frequency of 3000 + +``` +orbbec@agx:~/SensorSDK/build/install/Example/bin$ ./MultiDeviceSyncGmslTrigger +Please select options: +------------------------------------------------------------ + 0 --> config GMSL SOC hardware trigger Source. Set trigger fps: + 1 --> start Trigger + 2 --> stop Trigger + 3 --> exit +------------------------------------------------------------ +input select item: 0 + +Enter FPS (frames per second) (for example: 3000): 3000 +Setting FPS to 3000... +Please select options: +------------------------------------------------------------ + 0 --> config GMSL SOC hardware trigger Source. Set trigger fps: + 1 --> start Trigger + 2 --> stop Trigger + 3 --> exit +------------------------------------------------------------ +input select item: 1 + +write param: mode=1, fps=3000 +read param: mode=1, fps=3000 +start pwm source TriggerSync... + +Please select options: +------------------------------------------------------------ + 0 --> config GMSL SOC hardware trigger Source. Set trigger fps: + 1 --> start Trigger + 2 --> stop Trigger + 3 --> exit +------------------------------------------------------------ +input select item: + +``` + +**Brief description of configuring the trigger program** + +Please select options: + + 0 --> config GMSL SOC hardware trigger Source. Set trigger fps: + 1 --> start Trigger + 2 --> stop Trigger + 3 --> exit + +0: Configure the frequency of the SOC hardware trigger source. + +(config trigger frequency as 3000 (Note: It is recommended to configure the trigger frequency to be equal to or less than the set video frame frequency. For example, if the set video frame frequency is 30FPS, the trigger frequency should be 3000 or less.) + +1: Start hardware tigger. (start send hardware signal triggering at the configured trigger frequency) + +2: Stop triggering + +3: Exit the program diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync_gmsltrigger/ob_multi_devices_sync_gmsltrigger.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync_gmsltrigger/ob_multi_devices_sync_gmsltrigger.cpp new file mode 100644 index 0000000..4d32812 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.multi_devices_sync_gmsltrigger/ob_multi_devices_sync_gmsltrigger.cpp @@ -0,0 +1,167 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/* +Notes: MultiDeviceSyncGmslSocTrigger for GMSL device +on the nvidia arm64 xavier/orin platform ,this sample use nvidia platform soc pwm trigger to sync multi device. +*/ +#ifdef __linux__ +#include +#include +#include +#include +#include + +static const char * DEVICE_PATH = "/dev/camsync"; +static const uint8_t WRITE_MODE = 1; +static const uint8_t READ_MODE = 0; + +typedef struct { + uint8_t mode; + uint16_t fps; +} cs_param_t; + +class PwmTrigger { +public: + PwmTrigger() : fd(-1), isDeviceOpen(false) {} + ~PwmTrigger() { + closeDevice(); + } + int startTrigger(uint16_t triggerFps) { + if(!isDeviceOpen) { + if(openDevice() < 0) { + error("open device Failed!"); + return -1; + } + } + cs_param_t wt_param = { WRITE_MODE, triggerFps }; + int ret = writeTriggerParam(wt_param); + if(ret < 0) { + error("write trigger parameter Failed!"); + return ret; + } + info("write param: ", wt_param); + + cs_param_t rd_param = { READ_MODE, 0 }; + ret = readTriggerParam(rd_param); + if(ret < 0) { + error("read trigger parameter Failed!"); + } + info("read param: ", rd_param); + + return ret; + } + + int stopTrigger() { + int ret = 0; + if(isDeviceOpen) { + ret = closeDevice(); + } + return ret; + } + + void info(const std::string &msg, const cs_param_t ¶m) { + std::cout << msg << " mode=" << static_cast(param.mode) << ", fps=" << param.fps << std::endl; + } + void error(const std::string &msg) { + std::cerr << "Error: " << msg << std::endl; + } + +private: + int openDevice() { + fd = open(DEVICE_PATH, O_RDWR); + if(fd < 0) { + error("open /dev/camsync failed"); + return fd; + } + isDeviceOpen = true; + return fd; + } + int closeDevice() { + if(isDeviceOpen) { + isDeviceOpen = false; + int ret = close(fd); + fd = -1; + if(ret < 0) { + error("close /dev/camsync failed: " + std::to_string(errno)); + return ret; + } + } + return 0; + } + int writeTriggerParam(const cs_param_t ¶m) { + int ret = write(fd, ¶m, sizeof(param)); + if(ret < 0) { + error("write /dev/camsync failed: " + std::to_string(errno)); + } + return ret; + } + int readTriggerParam(cs_param_t ¶m) { + int ret = read(fd, ¶m, sizeof(param)); + if(ret < 0) { + error("read /dev/camsync failed: " + std::to_string(errno)); + } + return ret; + } + +private: + int fd; + bool isDeviceOpen; +}; + +int main(void) try { + PwmTrigger pwmTrigger; + uint16_t fps = 0; + constexpr std::streamsize maxInputIgnore = 10000; + while(true) { + std::cout << "Please select options: \n" + << "------------------------------------------------------------\n" + << " 0 --> config GMSL SOC PWM trigger Source. Set trigger fps: \n" + << " 1 --> start Trigger \n" + << " 2 --> stop Trigger \n" + << " 3 --> exit \n" + << "------------------------------------------------------------\n" + << "input select item: "; + int index = -1; + // std::cin >> index; + if(!(std::cin >> index)) { + std::cin.clear(); + std::cin.ignore(maxInputIgnore, '\n'); + std::cout << "Invalid input. Please enter a number." << std::endl; + continue; + } + std::cout << std::endl; + + switch(index) { + case 0: + std::cout << "Enter FPS (frames per second) (for example: 3000): "; + std::cin >> fps; // set the FPS here + std::cout << "Setting FPS to " << fps << "..." << std::endl; + break; + case 1: + if(pwmTrigger.startTrigger(fps) < 0) { + std::cerr << "Failed to start trigger." << std::endl; + } + std::cout << "start pwm source TriggerSync... \n" << std::endl; + break; + case 2: + pwmTrigger.stopTrigger(); + std::cout << "stop pwm source TriggerSync... \n" << std::endl; + break; + case 3: + pwmTrigger.stopTrigger(); + std::cout << "Program exit & clse device! \n" << std::endl; + return 0; + default: + std::cout << "-input Invalid index. \n" + << "-Please re-select and input valid param. \n"; + } + } + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + exit(EXIT_FAILURE); +} + +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.point_cloud/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.point_cloud/CMakeLists.txt new file mode 100644 index 0000000..5bde0a2 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.point_cloud/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_point_cloud) + +add_executable(${PROJECT_NAME} point_cloud.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.point_cloud/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.point_cloud/README.md new file mode 100644 index 0000000..ad2df1b --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.point_cloud/README.md @@ -0,0 +1,56 @@ +# C++ Sample: 3.advanced.point_clout + +## Overview + +Connect the device to open the stream, generate a depth point cloud or RGBD point cloud and save it as a ply format file, and exit the program through the ESC\_KEY key + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions + +## Code overview + +1. Create Align Filter and point cloud Filter. + + ```cpp + // Create a point cloud Filter, which will be used to generate pointcloud frame from depth and color frames. + auto pointCloud = std::make_shared(); + auto align = std::make_shared(OB_STREAM_COLOR); // align depth frame to color frame + ``` + +2. Create RGBD Point Cloud.Note that the AlignFilter processing is needed before the PointCloud processing. + + ```cpp + // align depth frame to color frame + auto alignedFrameset = align->process(frameset); + + // set to create RGBD point cloud format (will be effective only if color frame and depth frame are contained in the frameset) + pointCloud->setCreatePointFormat(OB_FORMAT_RGB_POINT); + + // process the frameset to generate point cloud frame + std::shared_ptr frame = pointCloud->process(alignedFrameset); + ``` + +3. create Depth PointCloud + + ```cpp + // set to create depth point cloud format + auto alignedFrameset = align->process(frameset); + + // set to create point cloud format + pointCloud->setCreatePointFormat(OB_FORMAT_POINT); + + // process the frameset to generate point cloud frame (pass into a single depth frame to process is also valid) + std::shared_ptr frame = pointCloud->process(alignedFrameset); + ``` + +## Run Sample + +Press R or r to create RGBD PointCloud and save to ply file! +Press D or d to create Depth PointCloud and save to ply file! + +Press ESC to exit! + +### Result + +![image](../../docs/resource/point_cloud.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.point_cloud/point_cloud.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.point_cloud/point_cloud.cpp new file mode 100644 index 0000000..c146edb --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.point_cloud/point_cloud.cpp @@ -0,0 +1,139 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include +#include "libobsensor/hpp/Utils.hpp" +#include "utils.hpp" + +#include +#include + +#define KEY_ESC 27 +#define KEY_R 82 +#define KEY_r 114 + +int main(void) try { + + // create config to configure the pipeline streams + auto config = std::make_shared(); + + // enable depth and color streams with specified format + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_ANY); + config->enableVideoStream(OB_STREAM_COLOR, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_RGB); + + // set frame aggregate output mode to all type frame require. therefor, the output frameset will contain all type of frames + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + + // create pipeline to manage the streams + auto pipeline = std::make_shared(); + + // Enable frame synchronization to ensure depth frame and color frame on output frameset are synchronized. + pipeline->enableFrameSync(); + + // Start pipeline with config + pipeline->start(config); + + // Create a point cloud Filter, which will be used to generate pointcloud frame from depth and color frames. + auto pointCloud = std::make_shared(); + + // Create a Align Filter, which will be used to align depth frame and color frame. + auto align = std::make_shared(OB_STREAM_COLOR); // align depth frame to color frame + + // operation prompt + std::cout << "Depth and Color stream are started!" << std::endl; + std::cout << "Press R or r to create RGBD PointCloud and save to ply file! " << std::endl; + std::cout << "Press D or d to create Depth PointCloud and save to ply file! " << std::endl; + std::cout << "Press M or m to create RGBD PointCloud and save to Mesh ply file! " << std::endl; + std::cout << "Press ESC to exit! " << std::endl; + + while(true) { + auto key = ob_smpl::waitForKeyPressed(); + if(key == 27) { + break; + } + + if(key == 'r' || key == 'R') { + std::cout << "Save RGBD PointCloud to ply file, this will take some time..." << std::endl; + + std::shared_ptr frameset = nullptr; + while(true) { + frameset = pipeline->waitForFrameset(1000); + if(frameset) { + break; + } + } + + // align depth frame to color frame + auto alignedFrameset = align->process(frameset); + + // set to create RGBD point cloud format (will be effective only if color frame and depth frame are contained in the frameset) + pointCloud->setCreatePointFormat(OB_FORMAT_RGB_POINT); + + // process the frameset to generate point cloud frame + std::shared_ptr frame = pointCloud->process(alignedFrameset); + + // save point cloud data to ply file + ob::PointCloudHelper::savePointcloudToPly("RGBPoints.ply", frame, false, false, 50); + + std::cout << "RGBPoints.ply Saved" << std::endl; + } + else if(key == 'd' || key == 'D') { + std::cout << "Save Depth PointCloud to ply file, this will take some time..." << std::endl; + + std::shared_ptr frameset = nullptr; + while(true) { + frameset = pipeline->waitForFrameset(1000); + if(frameset) { + break; + } + } + + // set to create depth point cloud format + auto alignedFrameset = align->process(frameset); + + // set to create point cloud format + pointCloud->setCreatePointFormat(OB_FORMAT_POINT); + + // process the frameset to generate point cloud frame (pass into a single depth frame to process is also valid) + std::shared_ptr frame = pointCloud->process(alignedFrameset); + + // save point cloud data to ply file + ob::PointCloudHelper::savePointcloudToPly("DepthPoints.ply", frame, false, false, 50); + + std::cout << "DepthPoints.ply Saved" << std::endl; + } + else if(key == 'm' || key == 'M') { + std::cout << "Save RGBD PointCloud(mesh) to ply file, this will take some time..." << std::endl; + + std::shared_ptr frameset = nullptr; + while(true) { + frameset = pipeline->waitForFrameset(1000); + if(frameset) { + break; + } + } + + // align depth frame to color frame + auto alignedFrameset = align->process(frameset); + + // set to create RGBD point cloud format (will be effective only if color frame and depth frame are contained in the frameset) + pointCloud->setCreatePointFormat(OB_FORMAT_RGB_POINT); + + // process the frameset to generate point cloud frame + std::shared_ptr frame = pointCloud->process(alignedFrameset); + + ob::PointCloudHelper::savePointcloudToPly("ColorMeshPoints.ply", frame, false, true, 50); + std::cout << "ColorMeshPoints.ply Saved" << std::endl; + } + } + // stop the pipeline + pipeline->stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.post_processing/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.post_processing/CMakeLists.txt new file mode 100644 index 0000000..f703b13 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.post_processing/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_post_processing) + +add_executable(${PROJECT_NAME} post_processing.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.post_processing/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.post_processing/README.md new file mode 100644 index 0000000..b7e6960 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.post_processing/README.md @@ -0,0 +1,81 @@ +# C++ Sample: 3.advanced.post_processing + +## Overview + +Use the SDK interface to  demonstrate post-processing operations, display post-processed images, and exit the program using the ESC_KEY key + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions + +Frameset is a combination of different types of Frames + +win is used to display the frame data. + +## Code overview + +1. Get the device and sensor, and create the list of recommended filters for the sensor + + ```cpp + auto device = pipe.getDevice(); + auto sensor = device->getSensor(OB_SENSOR_DEPTH); + auto filterList = sensor->createRecommendedFilters(); + ``` + +2. The filter operation. + + - Get the type of filter + + ```cpp + filter->getName() + ``` + + - Get the Config Schema Vec object + + ```cpp + filter->getConfigSchemaVec() + ``` + + - Enable the filter + + ```cpp + filter->enable(tokens[1] == "on"); + ``` + + - Get the Config Value object by name. + + ```cpp + filter->getConfigValue(configSchema.name) + ``` + + - Get the Enable State of the filter. + + ```cpp + filter->isEnabled() + ``` + + - Set the filter config value by name. + + ```cpp + filter->setConfigValue(tokens[1], value); + ``` + +3. Apply the recommended filters to the depth frame + + ```cpp + auto processedFrame = depthFrame; + // Apply the recommended filters to the depth frame + for(auto &filter: filterList) { + if(filter->isEnabled()) { // Only apply enabled filters + processedFrame = filter->process(processedFrame); + } + } + ```` + +## Run Sample + +Press the button according to the interface prompts + +### Result + +![image](../../docs/resource/post_processing.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.post_processing/post_processing.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.post_processing/post_processing.cpp new file mode 100644 index 0000000..4bac846 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.post_processing/post_processing.cpp @@ -0,0 +1,195 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include + +bool quit_program = false; // Flag to signal the program to quit + +void printFiltersInfo(const std::vector> &filterList) { + std::cout << filterList.size() << " post processing filters recommended:" << std::endl; + for(auto &filter: filterList) { + std::cout << " - " << filter->getName() << ": " << (filter->isEnabled() ? "enabled" : "disabled") << std::endl; + auto configSchemaVec = filter->getConfigSchemaVec(); + + // Print the config schema for each filter + for(auto &configSchema: configSchemaVec) { + std::cout << " - {" << configSchema.name << ", " << configSchema.type << ", " << configSchema.min << ", " << configSchema.max << ", " + << configSchema.step << ", " << configSchema.def << ", " << configSchema.desc << "}" << std::endl; + } + filter->enable(false); // Disable the filter + } +} + +void filterControl(const std::vector> &filterList) { + auto printHelp = [&]() { + std::cout << "Available commands:" << std::endl; + std::cout << "- Enter `[Filter]` to list the config values for the filter" << std::endl; + std::cout << "- Enter `[Filter] on` or `[Filter] off` to enable/disable the filter" << std::endl; + std::cout << "- Enter `[Filter] list` to list the config schema for the filter" << std::endl; + std::cout << "- Enter `[Filter] [Config]` to show the config values for the filter" << std::endl; + std::cout << "- Enter `[Filter] [Config] [Value]` to set a config value" << std::endl; + std::cout << "- Enter `L`or `l` to list all available filters" << std::endl; + std::cout << "- Enter `H` or `h` to print this help message" << std::endl; + std::cout << "- Enter `Q` or `q` to quit" << std::endl; + }; + printHelp(); + while(!quit_program) { + std::cout << "---------------------------" << std::endl; + std::cout << "Enter your input (h for help): "; + + std::string input; + std::getline(std::cin, input); + if(input == "q" || input == "Q") { + quit_program = true; + break; + } + else if(input == "l" || input == "L") { + printFiltersInfo(filterList); + continue; + } + else if(input == "h" || input == "H") { + printHelp(); + continue; + } + + // Parse the input + std::vector tokens; + std::istringstream iss(input); + for(std::string token; iss >> token;) { + tokens.push_back(token); + } + if(tokens.empty()) { + continue; + } + + bool foundFilter = false; + for(auto &filter: filterList) { + if(filter->getName() == tokens[0]) { + foundFilter = true; + if(tokens.size() == 1) { // print list of configs for the filter + auto configSchemaVec = filter->getConfigSchemaVec(); + std::cout << "Config values for " << filter->getName() << ":" << std::endl; + for(auto &configSchema: configSchemaVec) { + std::cout << " - " << configSchema.name << ": " << filter->getConfigValue(configSchema.name) << std::endl; + } + } + else if(tokens.size() == 2 && (tokens[1] == "on" || tokens[1] == "off")) { // Enable/disable the filter + filter->enable(tokens[1] == "on"); + std::cout << "Success: Filter " << filter->getName() << " is now " << (filter->isEnabled() ? "enabled" : "disabled") << std::endl; + } + else if(tokens.size() == 2 && tokens[1] == "list") { // List the config values for the filter + auto configSchemaVec = filter->getConfigSchemaVec(); + std::cout << "Config schema for " << filter->getName() << ":" << std::endl; + for(auto &configSchema: configSchemaVec) { + std::cout << " - {" << configSchema.name << ", " << configSchema.type << ", " << configSchema.min << ", " << configSchema.max << ", " + << configSchema.step << ", " << configSchema.def << ", " << configSchema.desc << "}" << std::endl; + } + } + else if(tokens.size() == 2) { // Print the config schema for the filter + auto configSchemaVec = filter->getConfigSchemaVec(); + bool foundConfig = false; + for(auto &configSchema: configSchemaVec) { + if(configSchema.name == tokens[1]) { + foundConfig = true; + std::cout << "Config values for " << filter->getName() << "@" << configSchema.name << ":" + << filter->getConfigValue(configSchema.name) << std::endl; + break; + } + } + if(!foundConfig) { + std::cerr << "Error: Config " << tokens[1] << " not found for filter " << filter->getName() << std::endl; + } + } + else if(tokens.size() == 3) { // Set a config value + try { + double value = std::stod(tokens[2]); + filter->setConfigValue(tokens[1], value); + } + catch(const std::exception &e) { + std::cerr << "Error: " << e.what() << std::endl; + continue; + } + std::cout << "Success: Config value of " << tokens[1] << " for filter " << filter->getName() << " is set to " << tokens[2] << std::endl; + } + break; + } + } + if(!foundFilter) { + std::cerr << "Error: Filter " << tokens[0] << " not found" << std::endl; + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } +} + +int main() try { + // Create a pipeline with default device + ob::Pipeline pipe; + + // Get the device and sensor, and get the list of recommended filters for the sensor + auto device = pipe.getDevice(); + auto sensor = device->getSensor(OB_SENSOR_DEPTH); + auto filterList = sensor->createRecommendedFilters(); + + // Print the recommended filters + printFiltersInfo(filterList); + + // Create a config with depth stream enabled + std::shared_ptr config = std::make_shared(); + config->enableStream(OB_STREAM_DEPTH); + + // Start the pipeline with config + pipe.start(config); + + // Start the filter control loop on sub thread + std::thread filterControlThread(filterControl, filterList); + filterControlThread.detach(); + + // Create a window for rendering, and set the resolution of the window + ob_smpl::CVWindow win("PostProcessing", 1280, 720, ob_smpl::ARRANGE_ONE_ROW); + + while(win.run() && !quit_program) { + // Wait for up to 1000ms for a frameset in blocking mode. + auto frameSet = pipe.waitForFrameset(1000); + if(frameSet == nullptr) { + continue; + } + + // Get the depth frame from the frameset + auto depthFrame = frameSet->getFrame(OB_FRAME_DEPTH); + if(!depthFrame) { + continue; + } + + auto processedFrame = depthFrame; + // Apply the recommended filters to the depth frame + for(auto &filter: filterList) { + if(filter->isEnabled()) { // Only apply enabled filters + processedFrame = filter->process(processedFrame); + } + } + + // Push the frames to the window for showing + // Due to processedFrame type is same as the depthFrame, we should push it with different group id. + win.pushFramesToView(depthFrame, 0); + win.pushFramesToView(processedFrame, 1); + } + + // Stop the pipeline + pipe.stop(); + + quit_program = true; + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.preset/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.preset/CMakeLists.txt new file mode 100644 index 0000000..4f401e6 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.preset/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_preset) + +add_executable(${PROJECT_NAME} preset.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.preset/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.preset/README.md new file mode 100644 index 0000000..b16fca8 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.preset/README.md @@ -0,0 +1,39 @@ +# C++ Sample: 3.advanced.preset + +## Overview + +Use the SDK interface to set and get the preset value. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions + +## Code overview + +1. Get preset list from device. + + ```cpp + std::shared_ptr presetLists = device->getAvailablePresetList(); + ``` + +2. Get preset value from device. + + ```cpp + // Print current preset name. + std::cout << "Current PresetName: " << device->getCurrentPresetName() << std::endl; + ``` + +3. Set preset value to device. + + ```cpp + // Load preset. + device->loadPreset(presetName); + ``` + +## Run Sample + +Press the button according to the interface prompts + +### Result + +![image](../../docs/resource/preset.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.preset/preset.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.preset/preset.cpp new file mode 100644 index 0000000..89b1d27 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.preset/preset.cpp @@ -0,0 +1,64 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include + +int main() try { + // Create a pipeline with default device. + ob::Pipeline pipe; + + // Get the device from the pipeline. + std::shared_ptr device = pipe.getDevice(); + + while(true){ + + // Get preset list from device. + std::shared_ptr presetLists = device->getAvailablePresetList(); + if (presetLists && presetLists->getCount() == 0) { + std::cout << "The current device does not support preset mode" << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + return 0; + } + + std::cout << "Available Presets:" << std::endl; + for(uint32_t index = 0; index < presetLists->getCount(); index++) { + // Print available preset name. + std::cout << " - " << index << "." << presetLists->getName(index) << std::endl; + } + + // Print current preset name. + std::cout << "Current PresetName: " << device->getCurrentPresetName() << std::endl; + + std::cout << "Enter index of preset to load: "; + + // Select preset to load. + int inputOption = ob_smpl::getInputOption(); + auto presetName = presetLists->getName(inputOption); + + // Load preset. + device->loadPreset(presetName); + + // Print current preset name. + std::cout << "Current PresetName: " << device->getCurrentPresetName() << std::endl; + } + + // Stop Pipeline. + pipe.stop(); + + printf("\nProgram ended successfully. Press any key to exit."); + ob_smpl::getInputOption(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.sync_align/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.sync_align/CMakeLists.txt new file mode 100644 index 0000000..24f76e3 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.sync_align/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_sync_align) + +add_executable(${PROJECT_NAME} sync_align.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.sync_align/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.sync_align/README.md new file mode 100644 index 0000000..06ddbef --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.sync_align/README.md @@ -0,0 +1,66 @@ +# C++ Sample: 3.advanced.sync_align + +## Overview + +Use the SDK interface to demonstrate the synchronization and alignment of sensor data streams,display the aligned image,and exit the program using the ESC_KEY key. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions + +Frameset is a combination of different types of Frames + +win is used to display the frame data. + +C2D(Color to Depth)is the transformation from the color image coordinate system to the depth image coordinate system.To map the pixel positions in the color image to the corresponding positions in the depth image. This is commonly used to align color and depth images so that both types of information can be used in the same coordinate system. + +D2C(Depth to Color)is the transformation from the depth image coordinate system to the color image coordinate system.To map the pixel positions in the depth image to the corresponding positions in the color image. This transformation allows depth data to be applied to the color image, facilitating the annotation or analysis of depth information within the color image. + +## Code overview + +1. Set alignment mode + + ```cpp + // Create a filter to align depth frame to color frame + auto depth2colorAlign = std::make_shared(OB_STREAM_COLOR); + + // create a filter to align color frame to depth frame + auto color2depthAlign = std::make_shared(OB_STREAM_DEPTH); + ``` + +2. Set the callback function for the Align Filter to display the aligned frames in the window + + ```cpp + depth2colorAlign->setCallBack([&win](std::shared_ptr frame) { win.pushFramesToView(frame); }); + color2depthAlign->setCallBack([&win](std::shared_ptr frame) { win.pushFramesToView(frame); }); + ``` + +3. Perform alignment processing + + ```cpp + // Get filter according to the align mode + std::shared_ptr alignFilter = depth2colorAlign; + if(align_mode % 2 == 1) { + alignFilter = color2depthAlign; + } + + // push the frameset to the Align Filter to align the frames. + // The frameset will be processed in an internal thread, and the resulting frames will be asynchronously output via the callback function. + alignFilter->pushFrame(frameSet); + ``` + +## Run Sample + +Press the Esc key in the window to exit the program. +'T': Switch Align Mode. +'F': Toggle Synchronization. +'+/-': Adjust Transparency + +### Result + +Sync +![image](../../docs/resource/sync.jpg) +D2C +![image](../../docs/resource/d2c.jpg) +C2D +![image](../../docs/resource/c2d.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.sync_align/sync_align.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.sync_align/sync_align.cpp new file mode 100644 index 0000000..bed8504 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/3.advanced.sync_align/sync_align.cpp @@ -0,0 +1,101 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include +#include + +bool sync = false; +uint8_t align_mode = 0; + +// key press event processing +void handleKeyPress(ob_smpl::CVWindow &win, std::shared_ptr pipe, int key) { + ////Get the key value + if(key == 'F' || key == 'f') { + // Press the F key to switch synchronization + sync = !sync; + if(sync) { + // enable frame sync inside the pipeline, which is synchronized by frame timestamp + pipe->enableFrameSync(); + } + else { + // turn off sync + pipe->disableFrameSync(); + } + win.addLog("Sync: " + std::string(sync ? "On" : "Off")); + } + else if(key == 't' || key == 'T') { + // Press the T key to switch align mode + align_mode = (align_mode + 1) % 2; + win.addLog("Align Mode: " + std::string(align_mode == 0 ? "Depth to Color" : "Color to Depth")); + } +} + +int main(void) try { + // Configure which streams to enable or disable for the Pipeline by creating a Config + auto config = std::make_shared(); + + // enable depth and color streams with specified format + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_ANY); + config->enableVideoStream(OB_STREAM_COLOR, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_RGB); + + // set the frame aggregate output mode to ensure all types of frames are included in the output frameset + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + + // Create a pipeline with default device to manage stream + auto pipe = std::make_shared(); + + // Start the pipeline with config + pipe->start(config); + + // Create a window for rendering and set the resolution of the window + ob_smpl::CVWindow win("Sync&Align", 1280, 720, ob_smpl::ARRANGE_OVERLAY); + // set key prompt + win.setKeyPrompt("'T': Switch Align Mode, 'F': Toggle Synchronization, '+/-': Adjust Transparency"); + // set the callback function for the window to handle key press events + win.setKeyPressedCallback([&win, pipe](int key) { handleKeyPress(win, pipe, key); }); + + // Create a filter to align depth frame to color frame + auto depth2colorAlign = std::make_shared(OB_STREAM_COLOR); + + // create a filter to align color frame to depth frame + auto color2depthAlign = std::make_shared(OB_STREAM_DEPTH); + + // Set the callback function for the Align Filter to display the aligned frames in the window + depth2colorAlign->setCallBack([&win](std::shared_ptr frame) { win.pushFramesToView(frame); }); + color2depthAlign->setCallBack([&win](std::shared_ptr frame) { win.pushFramesToView(frame); }); + + while(win.run()) { + // Wait for a frameset from the pipeline + auto frameSet = pipe->waitForFrameset(100); + if(frameSet == nullptr) { + continue; + } + + // Get filter according to the align mode + std::shared_ptr alignFilter = depth2colorAlign; + if(align_mode % 2 == 1) { + alignFilter = color2depthAlign; + } + + // push the frameset to the Align Filter to align the frames. + // The frameset will be processed in an internal thread, and the resulting frames will be asynchronously output via the callback function. + alignFilter->pushFrame(frameSet); + } + + // Stop the Pipeline, no frame data will be generated + pipe->stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.logger/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.logger/CMakeLists.txt new file mode 100644 index 0000000..b7b6bde --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.logger/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_logger) + +add_executable(${PROJECT_NAME} logger.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.logger/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.logger/README.md new file mode 100644 index 0000000..bf6ad42 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.logger/README.md @@ -0,0 +1,102 @@ +# C++ Sample: 4.msic.loggger + +## Overview +Use the SDK context class or modify the XML configuration file to set the SDK log level, including the log level output to the terminal and the log level output to the file. + +### Knowledge +Context, which serves as the entry point to the underlying SDK. It is used to query device lists, handle device callbacks, and set the log level. + +## Code Overview + +1. Configure the log level output to the terminal + + ```cpp + // Configure the log level output to the terminal. + ob::Context::setLoggerToConsole(OB_LOG_SEVERITY_ERROR); + ``` + +2. Configure the log level output to the file. +The default output path settings(Windows/Linux) is `${CWD}/Log/OrbbecSDK.log`, where `${CWD}` represents the Current Working Directory. For Android, the path is `/sdcard/orbbec/Log`. + + ```cpp + // Configure the log level and path output to the file. + // The first parameter is the log level, and the second parameter is the output path. + // If the output path is empty, the existing settings will continue to be used (if the existing configuration is also empty, the log will not be output to the file). + ob::Context::setLoggerToFile(OB_LOG_SEVERITY_DEBUG, "Log/Custom/"); + ``` + +3. Registering a log callback + + ```cpp + // Register a log callback, you can get log information in the callback. + // The first parameter is the log level, and the second parameter is the callback function. + ob::Context::setLoggerToCallback(OB_LOG_SEVERITY_DEBUG, [](OBLogSeverity severity, const char *logMsg) { + std::cout << "[CallbackMessage][Level:" << severity << "]" << logMsg; + }); + ``` + +## Configuration via XML + +### Priority of Log Level Configuration +**1. API Overrides:** +If the log level is set through the SDK's API, it overrides the XML configuration. The final log level will always follow the API setting, even if the XML file specifies a different level. + +**2. XML Configuration Priority:** +The node in the XML file sets the default log level and output parameters. This setting is used if the log level is not explicitly set via the API. + +### Overview of XML Configuration +The log level and other logging parameters can be configured via the XML configuration file. After compiling and installing the project using `cmake install`, you will find the configuration files (`OrbbecSDKConfig.xml` and `OrbbecSDKConfig.md`) in the following locations: + +- `bin` directory +- `shared` directory + +Additionally, the original configuration file can be located in the source directory at: +```bash + OrbbecSDK_v2/src/shared/environment/OrbbecSDKConfig.xml +``` +**To ensure proper loading, place the `OrbbecSDKConfig.xml` file in the same directory as your executable.** + +### Log Level Configuration +Open the `OrbbecSDKConfig.xml` file and locate the `` node. The configuration should look like the following example: + +```xml + + + + 5 + + 3 + + + + 100 + + 3 + + false + +``` + +#### Configuration Details +**1. Log Levels**: +- `FileLogLevel`: Controls the logging level for file output. +- `ConsoleLogLevel`: Controls the logging level for console output. + +**2. File Output Parameters**: + - `MaxFileSize`: Maximum size of a single log file in MB. + - `MaxFileNum`: Maximum number of log files before old logs are overwritten (circular overwrite). + +**3. Asynchronous Logging**: +- Enabling asynchronous logging (`true`) can reduce blocking during log output but may result in log loss if the program exits abnormally. + +## Run Sample +If you are on Windows, you can switch to the directory `OrbbecSDK_v2/build/win_XX/bin` to find the `ob_logger.exe`. + +If you are on linux, you can switch to the directory `OrbbecSDK_v2/build/linux_XX/bin` to find the `ob_logger`. + +### Result +![result](/docs/resource/logger.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.logger/logger.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.logger/logger.cpp new file mode 100644 index 0000000..2557578 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.logger/logger.cpp @@ -0,0 +1,45 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include +#include + +int main() try { + // Configure the log level output to the terminal. + ob::Context::setLoggerToConsole(OB_LOG_SEVERITY_ERROR); + + // Configure the log level and path output to the file. + ob::Context::setLoggerToFile(OB_LOG_SEVERITY_DEBUG, "Log/Custom/"); + + // Register a log callback, you can get log information in the callback. + ob::Context::setLoggerToCallback(OB_LOG_SEVERITY_DEBUG, + [](OBLogSeverity severity, const char *logMsg) { std::cout << "[CallbackMessage][Level:" << severity << "]" << logMsg; }); + + // Configure which streams to enable or disable for the Pipeline by creating a Config + std::shared_ptr config = std::make_shared(); + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_Y16); + config->enableVideoStream(OB_STREAM_COLOR, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_RGB); + + // Create a pipeline with default device to manage stream + std::shared_ptr pipe = std::make_shared(); + + // Start the pipeline with config + pipe->start(config); + // Stop the Pipeline, no frame data will be generated + pipe->stop(); + + ob::Context::setLoggerToCallback(OB_LOG_SEVERITY_OFF, nullptr); + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.metadata/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.metadata/CMakeLists.txt new file mode 100644 index 0000000..5b3b0b5 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.metadata/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_metadata) + +add_executable(${PROJECT_NAME} metadata.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.metadata/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.metadata/README.md new file mode 100644 index 0000000..a499b7d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.metadata/README.md @@ -0,0 +1,71 @@ +# C++ Sample: 4.misc.metadata + +## Overview + +Use the SDK interface to get the frameSet, then get the frame from frameSet, print the value of the frame metadata and exit the program using the ESC_KEY key. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions. + +Frameset is a combination of different types of Frames. + +Metadata is used to describe the various properties and states of a frame. + +## Code overview + +1. Create an ob::Pipeline object, and start the pipeline. + + ```cpp + // Create a pipeline. + ob::Pipeline pipe; + + // Start the pipeline with default config. + // Modify the default configuration by the configuration file: "OrbbecSDKConfig.xml" + pipe.start(); + ``` + +2. Get frameSet from pipeline. + + ```cpp + // Wait for frameSet from the pipeline, the default timeout is 1000ms. + auto frameSet = pipe.waitForFrameset(); + ``` + +3. Get frame from frameSet. + + ```cpp + auto frameCount = frameSet->getCount(); + for(uint32_t i = 0; i < frameCount; i++) { + // Get the frame from frameSet + auto frame = frameSet->getFrame(i); + } + ``` + +4. Check if the frame object contains metadata, then retrieve it. + + ```cpp + // Get the metadata of the frame + for(uint32_t j = 0; j < static_cast(metadataCount); j++) { + // If the frame has the metadata, get the metadata value + if(frame->hasMetadata(static_cast(j))) { + std::cout << "metadata type: " << std::left << std::setw(50) << metadataTypeMap[j] + << " metadata value: " << frame->getMetadataValue(static_cast(j)) << std::endl; + } + } + ``` + +6. Stop pipeline + + ```cpp + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + ``` + +## Run Sample + +Press the Esc key in the window to exit the program. + +### Result + +![image](../../docs/resource/metadata.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.metadata/metadata.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.metadata/metadata.cpp new file mode 100644 index 0000000..c61660b --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.metadata/metadata.cpp @@ -0,0 +1,175 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include +#include + +#include "utils.hpp" + +#include +#include +#include +#include + +std::thread inputWatchThread; +std::atomic isRunning(true); +void inputWatcher(); +const char *metadataTypeToString(OBFrameMetadataType type); +const char *frameTypeToString(OBFrameType type); + +int main() try { + // Create a pipeline. + ob::Pipeline pipe; + + // Start the pipeline with default config. + // Modify the default configuration by the configuration file: "OrbbecSDKConfig.xml" + pipe.start(); + + // Get key input + inputWatchThread = std::thread(inputWatcher); + inputWatchThread.detach(); + + while(isRunning) { + // Wait for frameSet from the pipeline, the default timeout is 1000ms. + auto frameSet = pipe.waitForFrameset(); + if(!frameSet) { + continue; + } + + // Get the count of frames in the frameSet + auto frameCount = frameSet->getCount(); + + for(uint32_t i = 0; i < frameCount; i++) { + // Get the frame from frameSet + auto frame = frameSet->getFrame(i); + auto frameIndex = frame->index(); + // Get the metadata of the frame, and print the metadata every 30 frames + if(frameIndex % 30 == 0) { + std::cout << std::endl; + std::cout << "frame type: " << frameTypeToString(frame->type()) << std::endl; + for(uint32_t j = 0; j < static_cast(OB_FRAME_METADATA_TYPE_COUNT); j++) { + // If the frame has the metadata, get the metadata value + if(frame->hasMetadata(static_cast(j))) { + std::cout << "metadata type: " << std::left << std::setw(50) << metadataTypeToString(static_cast(j)) + << " metadata value: " << frame->getMetadataValue(static_cast(j)) << std::endl; + } + } + } + } + } + // Stop the Pipeline, no frame data will be generated + pipe.stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +void inputWatcher() { + char input = ob_smpl::waitForKeyPressed(); + if(input == ESC_KEY) { + isRunning = false; + } +} + +const char *metadataTypeToString(OBFrameMetadataType type) { + switch(type) { + case OB_FRAME_METADATA_TYPE_TIMESTAMP: + return "OB_FRAME_METADATA_TYPE_TIMESTAMP"; + case OB_FRAME_METADATA_TYPE_SENSOR_TIMESTAMP: + return "OB_FRAME_METADATA_TYPE_SENSOR_TIMESTAMP"; + case OB_FRAME_METADATA_TYPE_FRAME_NUMBER: + return "OB_FRAME_METADATA_TYPE_FRAME_NUMBER"; + case OB_FRAME_METADATA_TYPE_AUTO_EXPOSURE: + return "OB_FRAME_METADATA_TYPE_AUTO_EXPOSURE"; + case OB_FRAME_METADATA_TYPE_EXPOSURE: + return "OB_FRAME_METADATA_TYPE_EXPOSURE"; + case OB_FRAME_METADATA_TYPE_GAIN: + return "OB_FRAME_METADATA_TYPE_GAIN"; + case OB_FRAME_METADATA_TYPE_AUTO_WHITE_BALANCE: + return "OB_FRAME_METADATA_TYPE_AUTO_WHITE_BALANCE"; + case OB_FRAME_METADATA_TYPE_WHITE_BALANCE: + return "OB_FRAME_METADATA_TYPE_WHITE_BALANCE"; + case OB_FRAME_METADATA_TYPE_BRIGHTNESS: + return "OB_FRAME_METADATA_TYPE_BRIGHTNESS"; + case OB_FRAME_METADATA_TYPE_CONTRAST: + return "OB_FRAME_METADATA_TYPE_CONTRAST"; + case OB_FRAME_METADATA_TYPE_SATURATION: + return "OB_FRAME_METADATA_TYPE_SATURATION"; + case OB_FRAME_METADATA_TYPE_SHARPNESS: + return "OB_FRAME_METADATA_TYPE_SHARPNESS"; + case OB_FRAME_METADATA_TYPE_BACKLIGHT_COMPENSATION: + return "OB_FRAME_METADATA_TYPE_BACKLIGHT_COMPENSATION"; + case OB_FRAME_METADATA_TYPE_HUE: + return "OB_FRAME_METADATA_TYPE_HUE"; + case OB_FRAME_METADATA_TYPE_GAMMA: + return "OB_FRAME_METADATA_TYPE_GAMMA"; + case OB_FRAME_METADATA_TYPE_POWER_LINE_FREQUENCY: + return "OB_FRAME_METADATA_TYPE_POWER_LINE_FREQUENCY"; + case OB_FRAME_METADATA_TYPE_LOW_LIGHT_COMPENSATION: + return "OB_FRAME_METADATA_TYPE_LOW_LIGHT_COMPENSATION"; + case OB_FRAME_METADATA_TYPE_MANUAL_WHITE_BALANCE: + return "OB_FRAME_METADATA_TYPE_MANUAL_WHITE_BALANCE"; + case OB_FRAME_METADATA_TYPE_ACTUAL_FRAME_RATE: + return "OB_FRAME_METADATA_TYPE_ACTUAL_FRAME_RATE"; + case OB_FRAME_METADATA_TYPE_FRAME_RATE: + return "OB_FRAME_METADATA_TYPE_FRAME_RATE"; + case OB_FRAME_METADATA_TYPE_AE_ROI_LEFT: + return "OB_FRAME_METADATA_TYPE_AE_ROI_LEFT"; + case OB_FRAME_METADATA_TYPE_AE_ROI_TOP: + return "OB_FRAME_METADATA_TYPE_AE_ROI_TOP"; + case OB_FRAME_METADATA_TYPE_AE_ROI_RIGHT: + return "OB_FRAME_METADATA_TYPE_AE_ROI_RIGHT"; + case OB_FRAME_METADATA_TYPE_AE_ROI_BOTTOM: + return "OB_FRAME_METADATA_TYPE_AE_ROI_BOTTOM"; + case OB_FRAME_METADATA_TYPE_EXPOSURE_PRIORITY: + return "OB_FRAME_METADATA_TYPE_EXPOSURE_PRIORITY"; + case OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_NAME: + return "OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_NAME"; + case OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_SIZE: + return "OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_SIZE"; + case OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_INDEX: + return "OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_INDEX"; + case OB_FRAME_METADATA_TYPE_LASER_POWER: + return "OB_FRAME_METADATA_TYPE_LASER_POWER"; + case OB_FRAME_METADATA_TYPE_LASER_POWER_LEVEL: + return "OB_FRAME_METADATA_TYPE_LASER_POWER_LEVEL"; + case OB_FRAME_METADATA_TYPE_LASER_STATUS: + return "OB_FRAME_METADATA_TYPE_LASER_STATUS"; + case OB_FRAME_METADATA_TYPE_GPIO_INPUT_DATA: + return "OB_FRAME_METADATA_TYPE_GPIO_INPUT_DATA"; + case OB_FRAME_METADATA_TYPE_DISPARITY_SEARCH_OFFSET: + return "OB_FRAME_METADATA_TYPE_DISPARITY_SEARCH_OFFSET"; + case OB_FRAME_METADATA_TYPE_DISPARITY_SEARCH_RANGE: + return "OB_FRAME_METADATA_TYPE_DISPARITY_SEARCH_RANGE"; + default: + return "unknown metadata type"; + } +} + +const char *frameTypeToString(OBFrameType type) { + switch(type) { + case OB_FRAME_VIDEO: + return "OB_FRAME_VIDEO"; + case OB_FRAME_IR: + return "OB_FRAME_IR"; + case OB_FRAME_COLOR: + return "OB_FRAME_COLOR"; + case OB_FRAME_DEPTH: + return "OB_FRAME_DEPTH"; + case OB_FRAME_ACCEL: + return "OB_FRAME_ACCEL"; + case OB_FRAME_GYRO: + return "OB_FRAME_GYRO"; + case OB_FRAME_IR_LEFT: + return "OB_FRAME_IR_LEFT"; + case OB_FRAME_IR_RIGHT: + return "OB_FRAME_IR_RIGHT"; + default: + return "unknown frame type"; + } +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.save_to_disk/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.save_to_disk/CMakeLists.txt new file mode 100644 index 0000000..79b0958 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.save_to_disk/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_save_to_disk) + +add_executable(${PROJECT_NAME} save_to_disk.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.save_to_disk/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.save_to_disk/README.md new file mode 100644 index 0000000..5da1a5f --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.save_to_disk/README.md @@ -0,0 +1,99 @@ +# C++ Sample: 4.misc.save_to_disk + +## Overview + +This sample demonstrates how to configure a pipeline to capture color and depth frames, perform necessary format conversions, and save the first 5 valid frames to disk in PNG format. The program discards initial frames to ensure stable stream capture and handles color format conversion for compatibility. + +### Knowledge + +Pipeline is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions. + +Frameset is a combination of different types of Frames. + +Metadata is used to describe the various properties and states of a frame. + +## Code overview + +1. Pipeline Configuration and Initialization. + + ```cpp + // Create a pipeline. + std::shared_ptr pipeline = std::make_shared(); + + // Create a config and enable the depth and color streams. + std::shared_ptr config = std::make_shared(); + config->enableStream(OB_STREAM_COLOR); + config->enableStream(OB_STREAM_DEPTH); + // Set the frame aggregate output mode to all type frame require. + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + ``` + +2. Get frameSet from pipeline. + + ```cpp + // Wait for frameSet from the pipeline, the default timeout is 1000ms. + auto frameSet = pipe.waitForFrameset(); + ``` + +3. Convert color images to RGB format and save them. + + ```cpp + // Get the depth and color frames. + auto depthFrame = frameSet->getFrame(OB_FRAME_DEPTH)->as(); + auto colorFrame = frameSet->getFrame(OB_FRAME_COLOR)->as(); + + // Convert the color frame to RGB format. + if(colorFrame->format() != OB_FORMAT_RGB) { + if(colorFrame->format() == OB_FORMAT_MJPG) { + formatConverter->setFormatConvertType(FORMAT_MJPG_TO_RGB); + } + else if(colorFrame->format() == OB_FORMAT_UYVY) { + formatConverter->setFormatConvertType(FORMAT_UYVY_TO_RGB); + } + else if(colorFrame->format() == OB_FORMAT_YUYV) { + formatConverter->setFormatConvertType(FORMAT_YUYV_TO_RGB); + } + else { + std::cout << "Color format is not support!" << std::endl; + continue; + } + colorFrame = formatConverter->process(colorFrame)->as(); + } + // Processed the color frames to BGR format, use OpenCV to save to disk. + formatConverter->setFormatConvertType(FORMAT_RGB_TO_BGR); + colorFrame = formatConverter->process(colorFrame)->as(); + ``` + +4. Save the first 5 valid frames to disk in PNG format. + + ```cpp + void saveDepthFrame(const std::shared_ptr depthFrame, const uint32_t frameIndex) { + std::vector params; + params.push_back(cv::IMWRITE_PNG_COMPRESSION); + params.push_back(0); + params.push_back(cv::IMWRITE_PNG_STRATEGY); + params.push_back(cv::IMWRITE_PNG_STRATEGY_DEFAULT); + std::string depthName = "Depth_" + std::to_string(depthFrame->width()) + "x" + std::to_string(depthFrame->height()) + "_" + std::to_string(frameIndex) + "_" + + std::to_string(depthFrame->timeStamp()) + "ms.png"; + cv::Mat depthMat(depthFrame->height(), depthFrame->width(), CV_16UC1, depthFrame->data()); + cv::imwrite(depthName, depthMat, params); + std::cout << "Depth saved:" << depthName << std::endl; + } + + void saveColorFrame(const std::shared_ptr colorFrame, const uint32_t frameIndex) { + std::vector params; + params.push_back(cv::IMWRITE_PNG_COMPRESSION); + params.push_back(0); + params.push_back(cv::IMWRITE_PNG_STRATEGY); + params.push_back(cv::IMWRITE_PNG_STRATEGY_DEFAULT); + std::string colorName = "Color_" + std::to_string(colorFrame->width()) + "x" + std::to_string(colorFrame->height()) + "_" + std::to_string(frameIndex) + "_" + + std::to_string(colorFrame->timeStamp()) + "ms.png"; + cv::Mat depthMat(colorFrame->height(), colorFrame->width(), CV_8UC3, colorFrame->data()); + cv::imwrite(colorName, depthMat, params); + std::cout << "Depth saved:" << colorName << std::endl; + } + ``` + +## Run Sample + +Press the Any key in the window to exit the program. \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.save_to_disk/save_to_disk.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.save_to_disk/save_to_disk.cpp new file mode 100644 index 0000000..f2319e7 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/4.misc.save_to_disk/save_to_disk.cpp @@ -0,0 +1,119 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "utils_opencv.hpp" + +#include + +void saveDepthFrame(const std::shared_ptr depthFrame, uint32_t frameIndex); +void saveColorFrame(const std::shared_ptr colorFrame, uint32_t frameIndex); + +int main() try { + // Create a pipeline. + std::shared_ptr pipeline = std::make_shared(); + + // Create a config and enable the depth and color streams. + std::shared_ptr config = std::make_shared(); + config->enableStream(OB_STREAM_COLOR); + config->enableStream(OB_STREAM_DEPTH); + // Set the frame aggregate output mode to all type frame require. + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + + uint32_t frameIndex = 0; + // Create a format converter filter. + auto formatConverter = std::make_shared(); + + // Start the pipeline. + pipeline->start(config); + + // Drop several frames + for(int i = 0; i < 15; ++i) { + auto lost = pipeline->waitForFrameset(100); + } + + while(true) { + // Wait for frameSet from the pipeline. + std::shared_ptr frameSet = pipeline->waitForFrameset(100); + + if(!frameSet) { + std::cout << "No frames received in 100ms..." << std::endl; + continue; + } + + if (++frameIndex >= 5) { + std::cout << "The demo is over, please press ESC to exit manually!" << std::endl; + break; + } + + // Get the depth and color frames. + auto depthFrame = frameSet->getFrame(OB_FRAME_DEPTH)->as(); + auto colorFrame = frameSet->getFrame(OB_FRAME_COLOR)->as(); + + // Convert the color frame to RGB format. + if(colorFrame->format() != OB_FORMAT_RGB) { + if(colorFrame->format() == OB_FORMAT_MJPG) { + formatConverter->setFormatConvertType(FORMAT_MJPG_TO_RGB); + } + else if(colorFrame->format() == OB_FORMAT_UYVY) { + formatConverter->setFormatConvertType(FORMAT_UYVY_TO_RGB); + } + else if(colorFrame->format() == OB_FORMAT_YUYV) { + formatConverter->setFormatConvertType(FORMAT_YUYV_TO_RGB); + } + else { + std::cout << "Color format is not support!" << std::endl; + continue; + } + colorFrame = formatConverter->process(colorFrame)->as(); + } + // Processed the color frames to BGR format, use OpenCV to save to disk. + formatConverter->setFormatConvertType(FORMAT_RGB_TO_BGR); + colorFrame = formatConverter->process(colorFrame)->as(); + + saveDepthFrame(depthFrame, frameIndex); + saveColorFrame(colorFrame, frameIndex); + } + + // Stop the Pipeline, no frame data will be generated + pipeline->stop(); + + std::cout << "Press any key to exit." << std::endl; + ob_smpl::waitForKeyPressed(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} + +void saveDepthFrame(const std::shared_ptr depthFrame, const uint32_t frameIndex) { + std::vector params; + params.push_back(cv::IMWRITE_PNG_COMPRESSION); + params.push_back(0); + params.push_back(cv::IMWRITE_PNG_STRATEGY); + params.push_back(cv::IMWRITE_PNG_STRATEGY_DEFAULT); + std::string depthName = "Depth_" + std::to_string(depthFrame->width()) + "x" + std::to_string(depthFrame->height()) + "_" + std::to_string(frameIndex) + "_" + + std::to_string(depthFrame->timeStamp()) + "ms.png"; + cv::Mat depthMat(depthFrame->height(), depthFrame->width(), CV_16UC1, depthFrame->data()); + cv::imwrite(depthName, depthMat, params); + std::cout << "Depth saved:" << depthName << std::endl; +} + +void saveColorFrame(const std::shared_ptr colorFrame, const uint32_t frameIndex) { + std::vector params; + params.push_back(cv::IMWRITE_PNG_COMPRESSION); + params.push_back(0); + params.push_back(cv::IMWRITE_PNG_STRATEGY); + params.push_back(cv::IMWRITE_PNG_STRATEGY_DEFAULT); + std::string colorName = "Color_" + std::to_string(colorFrame->width()) + "x" + std::to_string(colorFrame->height()) + "_" + std::to_string(frameIndex) + "_" + + std::to_string(colorFrame->timeStamp()) + "ms.png"; + cv::Mat depthMat(colorFrame->height(), colorFrame->width(), CV_8UC3, colorFrame->data()); + cv::imwrite(colorName, depthMat, params); + std::cout << "Color saved:" << colorName << std::endl; +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.open3d/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.open3d/CMakeLists.txt new file mode 100644 index 0000000..9d86376 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.open3d/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_open3d) + +find_package(Open3D REQUIRED) + +add_executable(${PROJECT_NAME} open3d.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils Open3D::Open3D) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.open3d/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.open3d/README.md new file mode 100644 index 0000000..08302b1 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.open3d/README.md @@ -0,0 +1,106 @@ +# C++ Sample: 5.wrapper.open3d + +## Overview + +This example shows how to capture synchronized color and depth frames from an Orbbec camera using the Orbbec SDK and render them in real time with Open3D's visualizer. + +### About Open3D +Open3D is an open-source library designed for 3D data processing. It provides: + + - Efficient data structures for 3D geometry (point clouds, meshes) + - Tensor-based operations with GPU support + - High-level visualization tools (e.g., VisualizerWithKeyCallback) + +For detailed installation instructions and advanced usage, visit the [Open3D GitHub repository](https://github.com/isl-org/Open3D). + +### Knowledge + + - Pipeline: Manages multi-channel stream configuration, frame aggregation, and synchronization. + + - Frameset: A collection of synchronized frames of different types (e.g., color, depth). + + - Config: A set of parameters that define the behavior of the pipeline. + + +## Code overview + +1. Configure the output format of color and depth frames. + + ```cpp + auto pipeline = std::make_shared(); + + // Configure which streams to enable or disable for the Pipeline by creating a Config. + std::shared_ptr config = std::make_shared(); + + config->enableVideoStream(OB_STREAM_COLOR, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_RGB); + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_Y16); + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + pipeline->enableFrameSync(); + ``` + + The `OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE` mode ensures all frame types are present in each frameset. + + + +2. Retrieve frames. + + ```cpp + auto frameset = pipeline->waitForFrames(); + if(!frameset) { + continue; + } + + // Get the color and depth frames from the frameset. + auto colorFrame = frameset->getFrame(OB_FRAME_COLOR)->as(); + auto depthFrame = frameset->getFrame(OB_FRAME_DEPTH)->as(); + ``` + +3. Convert to Open3D format. + + ```cpp + std::shared_ptr preRgbd = std::make_shared(); + + preRgbd->color_ = + core::Tensor(static_cast(colorFrame->getData()), { colorFrame->getHeight(), colorFrame->getWidth(), 3 }, core::Dtype::UInt8); + preRgbd->depth_ = + core::Tensor(reinterpret_cast(depthFrame->getData()), { depthFrame->getHeight(), depthFrame->getWidth() }, core::Dtype::UInt16); + ``` + +4. Visualize. + + ```cpp + if(!windowsInited) { + if(!colorVis.CreateVisualizerWindow("Color", 1280, 720) || !colorVis.AddGeometry(colorImage)) { + return 0; + } + + if(!depthVis.CreateVisualizerWindow("Depth", 1280, 720) || !depthVis.AddGeometry(depthImage)) { + return 0; + } + windowsInited = true; + } + else { + colorVis.UpdateGeometry(colorImage); + depthVis.UpdateGeometry(depthImage); + } + depthVis.PollEvents(); + depthVis.UpdateRender(); + + colorVis.PollEvents(); + colorVis.UpdateRender(); + ``` +5. Shutdown. +Press `ESC` in either window to exit and stop the pipeline. + +## Build and run +```bash +mkdir build +cd build +cmake .. -DOB_BUILD_OPEN3D_EXAMPLES=ON -DOpen3D_DIR= +make -j4 +./ob_open3d +``` + +### Result + +![image](../../docs/resource/open3d.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.open3d/open3d.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.open3d/open3d.cpp new file mode 100644 index 0000000..b861d8b --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.open3d/open3d.cpp @@ -0,0 +1,105 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "open3d/Open3D.h" + +#include +#include +#include +#include +#include + +using namespace open3d; +using legacyImage = open3d::geometry::Image; + +bool windowsInited = false; +bool isRunning = true; + +int main(void) try { + // Create a PipeLine with default device. + auto pipeline = std::make_shared(); + + // Configure which streams to enable or disable for the Pipeline by creating a Config. + std::shared_ptr config = std::make_shared(); + + config->enableVideoStream(OB_STREAM_COLOR, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_RGB); + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_Y16); + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + pipeline->enableFrameSync(); + + // Start the pipeline with config. + pipeline->start(config); + + open3d::visualization::VisualizerWithKeyCallback colorVis, depthVis; + + colorVis.RegisterKeyCallback(GLFW_KEY_ESCAPE, [&](visualization::Visualizer *vis) { + (void)vis; + isRunning = false; + return true; + }); + depthVis.RegisterKeyCallback(GLFW_KEY_ESCAPE, [&](visualization::Visualizer *vis) { + (void)vis; + isRunning = false; + return true; + }); + + while(isRunning) { + // Wait for a frameset to be available. + auto frameset = pipeline->waitForFrames(); + if(!frameset) { + continue; + } + + // Get the color and depth frames from the frameset. + auto colorFrame = frameset->getFrame(OB_FRAME_COLOR)->as(); + auto depthFrame = frameset->getFrame(OB_FRAME_DEPTH)->as(); + + // Create an RGBD image in Open3D tensor format. + std::shared_ptr preRgbd = std::make_shared(); + + preRgbd->color_ = + core::Tensor(static_cast(colorFrame->getData()), { colorFrame->getHeight(), colorFrame->getWidth(), 3 }, core::Dtype::UInt8); + preRgbd->depth_ = + core::Tensor(reinterpret_cast(depthFrame->getData()), { depthFrame->getHeight(), depthFrame->getWidth() }, core::Dtype::UInt16); + + // Convert the RGBD image to legacy Open3D image format. + auto imRgbd = preRgbd->ToLegacy(); + + auto colorImage = std::shared_ptr(&imRgbd.color_, [](legacyImage *) {}); + auto depthImage = std::shared_ptr(&imRgbd.depth_, [](legacyImage *) {}); + + if(!windowsInited) { + if(!colorVis.CreateVisualizerWindow("Color", 1280, 720) || !colorVis.AddGeometry(colorImage)) { + return 0; + } + + if(!depthVis.CreateVisualizerWindow("Depth", 1280, 720) || !depthVis.AddGeometry(depthImage)) { + return 0; + } + windowsInited = true; + } + else { + colorVis.UpdateGeometry(colorImage); + depthVis.UpdateGeometry(depthImage); + } + depthVis.PollEvents(); + depthVis.UpdateRender(); + + colorVis.PollEvents(); + colorVis.UpdateRender(); + } + + // Stop the PipeLine, no frame data will be generated. + pipeline->stop(); + + return 0; +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/CMakeLists.txt new file mode 100644 index 0000000..7c5cde5 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +add_subdirectory(imshow) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/README.md new file mode 100644 index 0000000..75d4335 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/README.md @@ -0,0 +1,7 @@ +# OpenCV Example for OpenOrbbecSDK + +The examples in this folder are intended to enhance the existing SDK samples by showing how OrbbecSDK cameras can be integrated with OpenCV for computer vision applications. + +## Project Structure + +- [imshow](./imshow/README.md): This example shows how to use OpenCV's `imshow` function to display the camera feed in a window. \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/common/cvhelper.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/common/cvhelper.hpp new file mode 100644 index 0000000..6ef02c0 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/common/cvhelper.hpp @@ -0,0 +1,66 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include +#include + +class CvHelper { +public: + CvHelper() = default; + ~CvHelper() = default; + +public: + // Helper function to convert depth frame to colorized image + static cv::Mat colorizeDepth(std::shared_ptr depthFrame) { + cv::Mat rstMat; + cv::Mat cvtMat; + + // depth frame pixel value multiply scale to get distance in millimeter + const float scale = depthFrame->as()->getValueScale(); + + // Get raw depth data from depth frame + cv::Mat rawMat = frameToMat(depthFrame); + + // normalization to 0-255. 0.032f is 256/8000, to limit the range of depth to 8000mm + rawMat.convertTo(cvtMat, CV_32F, scale * 0.032f); + + // apply gamma correction to enhance the contrast for near objects + cv::pow(cvtMat, 0.6f, cvtMat); + + // convert to 8-bit + cvtMat.convertTo(cvtMat, CV_8UC1, 10); // multiplier 10 is to normalize to 0-255 (nearly) after applying gamma correction + + // apply colormap + cv::applyColorMap(cvtMat, rstMat, cv::COLORMAP_JET); + + return rstMat; + } + + // Convert frame to cv::Mat + static cv::Mat frameToMat(std::shared_ptr frame) { + auto vf = frame->as(); + const uint32_t height = vf->getHeight(); + const uint32_t width = vf->getWidth(); + + if (frame->getFormat() == OB_FORMAT_BGR) { + return cv::Mat(height, width, CV_8UC3, frame->getData()); + } + else if(frame->getFormat() == OB_FORMAT_RGB) { + // The color channel for RGB images in opencv is BGR, so we need to convert it to BGR before returning the cv::Mat + auto rgbMat = cv::Mat(height, width, CV_8UC3, frame->getData()); + cv::Mat bgrMat; + cv::cvtColor(rgbMat, bgrMat, cv::COLOR_RGB2BGR); + return bgrMat; + } + else if(frame->getFormat() == OB_FORMAT_Y16) { + return cv::Mat(height, width, CV_16UC1, frame->getData()); + } + + std::cerr << "Unsupported format: " << frame->getFormat() << std::endl; + return cv::Mat(); + } +}; \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/imshow/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/imshow/CMakeLists.txt new file mode 100644 index 0000000..7bebddf --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/imshow/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_imshow) + +add_executable(${PROJECT_NAME} imshow.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/imshow/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/imshow/README.md new file mode 100644 index 0000000..6b1614a --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/imshow/README.md @@ -0,0 +1,34 @@ +# C++ Sample: 5.wrapper.opencv.imshow + +## Overview +This example shows how to use OpenCV's `imshow` function to display the camera feed in a window. + +## Code Overview + +1. Create a `Config` object to enable the depth stream with the desired format. + + ```cpp + // Configure which streams to enable or disable for the Pipeline by creating a Config + auto config = std::make_shared(); + + // enable depth stream with specified format + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_Y16); + ``` +2. Create a `Pipeline` object with the `config` object. + + ```cpp + // Create a Pipeline with the Config object + auto pipeline = std::make_shared(); + pipeline->configure(config); + ``` +3. Colorize the depth frame and display it using OpenCV's `imshow` function. + ```cpp + cv::Mat depthVisualized = CvHelper::colorizeDepth(depthFrame); + cv::imshow("ImageShow", depthVisualized); + ``` +## Run Sample + +Press the q or Q key in the window to exit the program. + +### Result +![result](/docs/resource/imshow.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/imshow/imshow.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/imshow/imshow.cpp new file mode 100644 index 0000000..ea4f73a --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.opencv/imshow/imshow.cpp @@ -0,0 +1,45 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" +#include "../common/cvhelper.hpp" + +#include +#include + +int main(void) try { + // Configure which streams to enable or disable for the Pipeline by creating a Config + auto config = std::make_shared(); + + // enable depth stream with specified format + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_ANY); + + std::shared_ptr pipeline = std::make_shared(); + pipeline->start(config); + + while(true) { + auto frames = pipeline->waitForFrames(); + auto depthFrame = frames->getFrame(OB_FRAME_DEPTH); + + // Color mapping the depth data in order to visualize it intuitively + cv::Mat depthVisualized = CvHelper::colorizeDepth(depthFrame); + + cv::imshow("ImageShow", depthVisualized); + int key = cv::waitKey(1); + if(key == 27 || key == 'q' || key == 'Q') { + break; + } + } + + pipeline->stop(); + + exit(EXIT_SUCCESS); +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/CMakeLists.txt new file mode 100644 index 0000000..7b9f2f1 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/CMakeLists.txt @@ -0,0 +1,4 @@ +# set(PCL_DIR "user/lib/PCL 1.12.1/cmake") +message(STATUS "- Building PCL examples") +add_subdirectory(pcl) +add_subdirectory(pcl_color) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/README.md new file mode 100644 index 0000000..9fc17af --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/README.md @@ -0,0 +1,37 @@ +# C++ Sample: 5.wrapper.pcl + +This project provides a set of examples and utilities for working with the Orbbec SDK and PCL (Point Cloud Library). It includes various modules for processing depth, color, and point cloud data. + +## Project Structure + +The project is organized into several subdirectories, each corresponding to different functionalities: + +- [Pcl](./pcl/README.md): Contains PCL-related functionality for processing point clouds and depth data. +- [Pcl-Color](./pcl_color/README.md): Contains PCL-related functionality specifically for handling color point clouds. + +## Building the Project + +### Step 1: Install PCL +More information on installing PCL can be found [here](https://github.com/PointCloudLibrary/pcl). + +If you are using windows, you can modify the `PCL_DIR` value in the `CMakeLists.txt` file. +```CMake +if(OB_BUILD_PCL) + # set(PCL_DIR "user/lib/PCL 1.12.1/cmake") + message(STATUS "- Building PCL examples") + add_subdirectory(pcl) + add_subdirectory(pcl_color) +endif() +``` +Note: If you need to run it, you may need to copy the dependent dlls to the same directory as the executable file. + +### Step 2: Configure the Project + +Find the following option and set it to ON. You can find this option in the `option.cmake` file under the `cmake` folder. +```CMake +option(OB_BUILD_PCL "Build Point Cloud Library examples" ON) +``` + +### Step 3: Build the OrbbecSDK +You can follow the [build guide](../../docs/tutorial/building_orbbec_sdk.md) file for more information on building the Orbbec SDK. + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl/CMakeLists.txt new file mode 100644 index 0000000..66153c1 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_options(/W3) +endif() + +project(ob_pcl) + +find_package(PCL REQUIRED) + +if (PCL_FOUND) + include_directories(${PCL_INCLUDE_DIRS}) + add_definitions(${PCL_DEFINITIONS}) + link_directories(${PCL_LIBRARY_DIRS}) +endif() +list(APPEND DEPENDENCIES ${PCL_LIBRARIES}) + +add_executable(${PROJECT_NAME} pcl.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl/README.md new file mode 100644 index 0000000..af4734a --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl/README.md @@ -0,0 +1,72 @@ +# C++ Sample: 5.wrapper.pcl + +## Overview + +Use OrbbecSDK to acquire point cloud data and convert the acquired point cloud data to PCL library point cloud data + + +## Code overview + +1. Create an ob::Pipeline object, and get frameSet from pipeline.. + + ```cpp + // create pipeline to manage the streams + auto pipeline = std::make_shared(); + + // Start pipeline with config + pipeline->start(config); + + // Wait for frames to be available + auto frameset = pipeline->waitForFrames(); + auto depth_frame = frameset->getFrame(OB_FRAME_DEPTH); + ``` + +2. Create a PointCloud object and convert the depth frame to point cloud data. + + ```cpp + // Create a point cloud Filter, which will be used to generate pointcloud frame from depth and color frames. + auto pointCloudFilter = std::make_shared(); + + // Set the format of the point cloud to be generated. + pointCloudFilter->setCreatePointFormat(OB_FORMAT_POINT); + auto result = pointCloudFilter->process(depth_frame); + ``` + +3. Convert the point cloud data to PCL library point cloud data. + + ```cpp + pcl::PointCloud::Ptr frameToPCL(std::shared_ptr frame) { + pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + + uint32_t pointsSize = frame->dataSize() / sizeof(OBPoint); + OBPoint *point = (OBPoint *)frame->data(); + + cloud->points.resize(pointsSize); + + for(uint32_t i = 0; i < pointsSize; i++) { + cloud->points[i].x = point->x; + cloud->points[i].y = point->y; + cloud->points[i].z = point->z; + + point++; + } + + return cloud; + } + ``` + +4. Render the point cloud data using PCL library. + + ```cpp + pcl::visualization::PCLVisualizer vis2("Depth Cloud"); + vis2.addPointCloud(pclPoint); + vis2.spin(); + ``` + +## Run Sample + +Press the q or Q key in the window to exit the program. + +### Result + +![image](../../../docs/resource/pcl.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl/pcl.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl/pcl.cpp new file mode 100644 index 0000000..0b961f4 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl/pcl.cpp @@ -0,0 +1,81 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +pcl::PointCloud::Ptr frameToPCL(std::shared_ptr frame) { + pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + + uint32_t pointsSize = frame->dataSize() / sizeof(OBPoint); + OBPoint *point = (OBPoint *)frame->data(); + + cloud->points.resize(pointsSize); + + for(uint32_t i = 0; i < pointsSize; i++) { + cloud->points[i].x = point->x; + cloud->points[i].y = point->y; + cloud->points[i].z = point->z; + + point++; + } + + return cloud; +} + +int main(void) try { + auto config = std::make_shared(); + + // enable depth and color streams with specified format + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_Y16); + + // create pipeline to manage the streams + auto pipeline = std::make_shared(); + + // Start pipeline with config + pipeline->start(config); + + // Wait for frames to be available + std::cout << "Waiting for frames arrive..." << std::endl; + auto frameset = pipeline->waitForFrameset(); + std::cout << "Frames arrive!" << std::endl; + + auto depthFrame = frameset->getFrame(OB_FRAME_DEPTH); + + pipeline->stop(); + + // Create a point cloud Filter, which will be used to generate pointcloud frame from depth and color frames. + auto pointCloudFilter = std::make_shared(); + + // Set the format of the point cloud to be generated. + pointCloudFilter->setCreatePointFormat(OB_FORMAT_POINT); + auto result = pointCloudFilter->process(depthFrame); + + // Convert the result frame to pcl point cloud + auto pclPoint = frameToPCL(result); + + // Using PCL library to visualize the point cloud. + pcl::visualization::PCLVisualizer vis2("Depth Cloud"); + vis2.addPointCloud(pclPoint); + vis2.spin(); + + exit(EXIT_SUCCESS); +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl_color/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl_color/CMakeLists.txt new file mode 100644 index 0000000..566cae6 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl_color/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_options(/W3) +endif() + +project(ob_pcl_color) + +find_package(PCL REQUIRED) + +if (PCL_FOUND) + include_directories(${PCL_INCLUDE_DIRS}) + add_definitions(${PCL_DEFINITIONS}) + link_directories(${PCL_LIBRARY_DIRS}) +endif() +list(APPEND DEPENDENCIES ${PCL_LIBRARIES}) + +add_executable(${PROJECT_NAME} pcl_color.cpp) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) +target_link_libraries(${PROJECT_NAME} ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl_color/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl_color/README.md new file mode 100644 index 0000000..91dab85 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl_color/README.md @@ -0,0 +1,101 @@ +# C++ Sample: 5.wrapper.pcl_color + +## Overview + +Use OrbbecSDK to acquire point cloud data and convert the acquired point cloud data to PCL library point cloud data + + +## Code overview + +1. Create an ob::Pipeline object, and get frameSet from pipeline.. + + ```cpp + // create pipeline to manage the streams + auto pipeline = std::make_shared(); + + // Enable frame synchronization to ensure depth frame and color frame on output frameset are synchronized. + pipeline->enableFrameSync(); + + // Start pipeline with config + pipeline->start(config); + + // Wait for frames to arrive + auto frameset = pipeline->waitForFrames(); + auto colorFrame = frameset->getFrame(OB_FRAME_COLOR); + ``` + +2. Create a PointCloud object and convert the depth frame to point cloud data. + + ```cpp + // Create a point cloud Filter, which will be used to generate pointcloud frame from depth and color frames. + auto pointCloudFilter = std::make_shared(); + // Create a align filter, which will be used to align depth frame to color frame. + auto alignFilter = std::make_shared(OB_STREAM_COLOR); + + // Align depth frame to color frame. + auto aliggnFrameset = alignFilter->process(frameset); + + // set to create RGBD point cloud format (will be effective only if color frame and depth frame are contained in the frameset) + pointCloudFilter->setCreatePointFormat(OB_FORMAT_RGB_POINT); + auto result = pointCloudFilter->process(aliggnFrameset); + ``` + +3. Convert the point cloud data to PCL library point cloud data. + + ```cpp + pcl::PointCloud::Ptr frameToPCL(std::shared_ptr pointsFrame, std::shared_ptr colorFrame) { + pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + + uint32_t pointsSize = pointsFrame->dataSize() / sizeof(OBColorPoint); + OBColorPoint *point = (OBColorPoint *)pointsFrame->data(); + + cloud->width = colorFrame->as()->width(); + cloud->height = colorFrame->as()->height(); + cloud->points.resize(pointsSize); + + for(uint32_t i = 0; i < pointsSize; i++) { + cloud->points[i].x = point->x; + cloud->points[i].y = point->y; + cloud->points[i].z = point->z; + cloud->points[i].r = point->r; + cloud->points[i].g = point->g; + cloud->points[i].b = point->b; + + point++; + } + + return cloud; + } + ``` + +4. Render the point cloud data using PCL library. + + ```cpp + void loadPCDFile() { + // Generate object to store cloud in .pcd file + pcl::PointCloud::Ptr cloudView(new pcl::PointCloud); + + pcl::io::loadPCDFile("./output.pcd", *cloudView); // Load .pcd File + + std::shared_ptr viewer(new pcl::visualization::PCLVisualizer("Captured Frame")); + + // Set background of viewer to black + viewer->setBackgroundColor(0, 0, 0); + // Add generated point cloud and identify with string "Cloud" + viewer->addPointCloud(cloudView, "Cloud"); + // Default size for rendered points + viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "Cloud"); + // Viewer Properties + viewer->initCameraParameters(); // Camera Parameters for ease of viewing + + viewer->spin(); // Allow user to rotate point cloud and view it + } + ``` + +## Run Sample + +Press the q or Q key in the window to exit the program. + +### Result + +![image](../../../docs/resource/pcl_color.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl_color/pcl_color.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl_color/pcl_color.cpp new file mode 100644 index 0000000..6538cdf --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/5.wrapper.pcl/pcl_color/pcl_color.cpp @@ -0,0 +1,133 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +pcl::PointCloud::Ptr frameToPCL(std::shared_ptr pointsFrame, std::shared_ptr colorFrame) { + pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + + uint32_t pointsSize = pointsFrame->dataSize() / sizeof(OBColorPoint); + OBColorPoint *point = (OBColorPoint *)pointsFrame->data(); + + cloud->width = colorFrame->as()->width(); + cloud->height = colorFrame->as()->height(); + cloud->points.resize(pointsSize); + + for(uint32_t i = 0; i < pointsSize; i++) { + cloud->points[i].x = point->x; + cloud->points[i].y = point->y; + cloud->points[i].z = point->z; + cloud->points[i].r = point->r; + cloud->points[i].g = point->g; + cloud->points[i].b = point->b; + + point++; + } + + return cloud; +} + +void loadPCDFile() { + // Generate object to store cloud in .pcd file + pcl::PointCloud::Ptr cloudView(new pcl::PointCloud); + + pcl::io::loadPCDFile("./output.pcd", *cloudView); // Load .pcd File + + std::shared_ptr viewer(new pcl::visualization::PCLVisualizer("RGB Cloud")); + + // Set background of viewer to black + viewer->setBackgroundColor(0, 0, 0); + // Add generated point cloud and identify with string "Cloud" + viewer->addPointCloud(cloudView, "Cloud"); + // Default size for rendered points + viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "Cloud"); + // Viewer Properties + viewer->initCameraParameters(); // Camera Parameters for ease of viewing + + viewer->spin(); // Allow user to rotate point cloud and view it +} + +int main(void) try { + auto config = std::make_shared(); + + // enable depth and color streams with specified format + config->enableVideoStream(OB_STREAM_DEPTH, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_Y16); + config->enableVideoStream(OB_STREAM_COLOR, OB_WIDTH_ANY, OB_HEIGHT_ANY, OB_FPS_ANY, OB_FORMAT_RGB); + + // set frame aggregate output mode to all type frame require. therefor, the output frameset will contain all type of frames + config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE); + + // create pipeline to manage the streams + auto pipeline = std::make_shared(); + + // Enable frame synchronization to ensure depth frame and color frame on output frameset are synchronized. + pipeline->enableFrameSync(); + + // Start pipeline with config + pipeline->start(config); + + // Drop several frames for auto-exposure + for(int i = 0; i < 10; ++i) { + auto lost = pipeline->waitForFrameset(); + } + + // Wait for frames to arrive + std::cout << "Waiting for frames..." << std::endl; + auto frameset = pipeline->waitForFrameset(); + std::cout << "Frames arrive!" << std::endl << std::endl; + auto colorFrame = frameset->getFrame(OB_FRAME_COLOR); + + pipeline->stop(); + + std::cout << "Wait for PointCloud Filter to generate PointCloud Frame..." << std::endl; + // Create a point cloud Filter, which will be used to generate pointcloud frame from depth and color frames. + auto pointCloudFilter = std::make_shared(); + // Create a align filter, which will be used to align depth frame to color frame. + auto alignFilter = std::make_shared(OB_STREAM_COLOR); + + // Align depth frame to color frame. + auto aliggnFrameset = alignFilter->process(frameset); + + // set to create RGBD point cloud format (will be effective only if color frame and depth frame are contained in the frameset) + pointCloudFilter->setCreatePointFormat(OB_FORMAT_RGB_POINT); + auto result = pointCloudFilter->process(aliggnFrameset); + + auto pclPoints = frameToPCL(result, colorFrame); + std::cout << "PointCloud Filter generated PointCloud Frame!" << std::endl << std::endl; + + std::cout << "It may cost some times to save and load the point cloud, please wait..." << std::endl; + // Save generated point cloud to .pcd file + pcl::io::savePCDFileASCII("./output.pcd", *pclPoints); + + // Load generated point cloud from .pcd file + loadPCDFile(); + + // 3D Point Cloud Visualization using PCLVisualizer + // pcl::visualization::PCLVisualizer vis2("Color Point Cloud"); + // vis2.addPointCloud(pclPoints); + // vis2.spin(); + + exit(EXIT_SUCCESS); +} +catch(ob::Error &e) { + std::cerr << "function:" << e.getFunction() << "\nargs:" << e.getArgs() << "\nmessage:" << e.what() << "\ntype:" << e.getExceptionType() << std::endl; + std::cout << "\nPress any key to exit."; + ob_smpl::waitForKeyPressed(); + exit(EXIT_FAILURE); +} \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/CMakeLists.txt new file mode 100644 index 0000000..4edeaae --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/CMakeLists.txt @@ -0,0 +1,71 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) + +file(GLOB subdirectories RELATIVE ${CMAKE_CURRENT_LIST_DIR} "*") + +# dependecies +find_package(OpenCV QUIET) +if(NOT ${OpenCV_FOUND}) + message(WARNING "OpenCV not found, some examples may not be built. Please install OpenCV or set OpenCV_DIR to the directory of your OpenCV installation.") +endif() + +# utils +add_subdirectory(utils) + +# c examples +add_subdirectory(c_examples) + +# cpp examples +add_subdirectory(0.basic.enumerate) +add_subdirectory(1.stream.imu) +add_subdirectory(2.device.control) +add_subdirectory(2.device.firmware_update) +add_subdirectory(2.device.forceip) +add_subdirectory(2.device.multi_devices_firmware_update) +add_subdirectory(2.device.optional_depth_presets_update) +add_subdirectory(2.device.record.nogui) +add_subdirectory(2.device.hot_plugin) +add_subdirectory(3.advanced.point_cloud) +add_subdirectory(3.advanced.preset) +add_subdirectory(4.misc.logger) +add_subdirectory(4.misc.metadata) +if(OB_BUILD_LINUX) + add_subdirectory(3.advanced.multi_devices_sync_gmsltrigger) +endif() + +if (OB_BUILD_PCL_EXAMPLES) + add_subdirectory(5.wrapper.pcl) +endif() + +if (OB_BUILD_OPEN3D_EXAMPLES) + add_subdirectory(5.wrapper.open3d) +endif() + +# cpp examples with OpenCV Requirements +if(${OpenCV_FOUND}) + add_subdirectory(0.basic.quick_start) + add_subdirectory(1.stream.depth) + add_subdirectory(1.stream.color) + add_subdirectory(1.stream.confidence) + add_subdirectory(1.stream.infrared) + add_subdirectory(1.stream.callback) + add_subdirectory(1.stream.multi_streams) + add_subdirectory(2.device.record) + add_subdirectory(2.device.playback) + add_subdirectory(3.advanced.common_usages) + add_subdirectory(3.advanced.sync_align) + add_subdirectory(3.advanced.hw_d2c_align) + add_subdirectory(3.advanced.post_processing) + add_subdirectory(3.advanced.coordinate_transform) + add_subdirectory(3.advanced.hdr) + add_subdirectory(3.advanced.laser_interleave) + add_subdirectory(3.advanced.multi_devices) + add_subdirectory(3.advanced.multi_devices_sync) + add_subdirectory(4.misc.save_to_disk) + add_subdirectory(5.wrapper.opencv) +endif() + +# InstallRequiredSystemLibraries +include(InstallRequiredSystemLibraries) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/README.md new file mode 100644 index 0000000..7c0c836 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/README.md @@ -0,0 +1,123 @@ +# Examples Code for Orbbec cameras + +## Introduction + +There are several examples codes for users learn how to use Orbbec cameras. Here is a brief introduction to each sample code: + +| Level | Sample | Description | +| -------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| basic | [quick_start](0.basic.quick_start) | Quick show how to use the SDK to get frames from the Orbbec RGB-D camera device. | +| basic | [enumerate](0.basic.enumerate) | Use the SDK interface to obtain camera-related information, including model, various sensors, and sensor-related configurations. | +| stream | [depth](1.stream.depth) | Use the SDK interface to obtain the depth stream of the camera and display it in the window. | +| stream | [color](1.stream.color) | Use the SDK interface to obtain the camera's color stream and display it in the window. | +| stream | [infrared](1.stream.infrared) | Use the SDK interface to obtain the camera IR stream and display it in the window. | +| stream | [confidence](1.stream.confidence) | Use the SDK interface to obtain the depth and confidence stream of the camera and display them in the window. | +| stream | [imu](1.stream.imu) | Use the SDK interface to obtain the camera's internal imu data and output it | +| stream | [multi_streams](1.stream.multi_streams) | Use SDK to obtain multiple camera data streams and output them. | +| stream | [callback](1.stream.callback) | In this sample, users can obtain depth, RGB, and IR images. This sample also supports performing user-defined operations such as data acquisition, data processing, and data modification within the callback function. | +| device | [control](2.device.control) | The SDK can be used to modify camera-related parameters, including laser switch, laser level intensity, white balance switch, etc. | +| device | [firmware_update](2.device.firmware_update) | This sample shows how to read a BIN file to perform firmware upgrades on the device. | +| device | [multi_devices_firmware_update](2.device.multi_devices_firmware_update) | This sample shows how to upgrade multiple Orbbec cameras connected to your system in a row. | +| device | [optional_depth_presets_update](2.device.optional_depth_presets_update) | This sample shows how to read a BIN file to perform optional depth presets upgrades on the device. | +| device | [device_forceip](2.device.forceip) | This example shows how to configure a new IP for an Orbbec GigE network device using the ForceIP command (as defined by the GigE Vision standard). | +| device | [hot_plugin](2.device.hot_plugin) | Use SDK to handle the settings of device unplug callback and process the acquired code stream after unplugging. | +| device | [device_playback](2.device.playback) | This example shows how to use the SDK to get frame data from a recorded Rosbag package.| +| device | [device_record](2.device.record) | This example shows how to record video stream data from an Orbbec camera into a Rosbag format packet. | +| device | [device_record_nogui](2.device.record.nogui) | This example shows how to record video stream data from an Orbbec camera into a Rosbag format packet. It is a command-line (CLI) tool that records streams directly without rendering video frames. | +| advanced | [sync_align](3.advanced.sync_align) | Use the SDK interface to demonstrate the synchronization and alignment of sensor data streams, display the aligned image. | +| advanced | [hw_d2c_align](3.advanced.hw_d2c_align) | Use the SDK interface to demonstrate the application of hardware depth-to-color alignment. | +| advanced | [post_processing](3.advanced.post_processing) | Use the SDK interface to  demonstrate post-processing operations, display post-processed images. | +| advanced | [point_cloud](3.advanced.point_cloud) | Connect the device to open the stream, generate a depth point cloud or RGBD point cloud and save it as a ply format file. | +| advanced | [preset](3.advanced.preset) | Use the SDK interface to set and get the preset value. | +| advanced | [coordinate_transform](3.advanced.coordinate_transform) | Use the SDK interface to transform different coordinate systems. | +| advanced | [hdr](3.advanced.hdr) | In this sample, user can get the HDR merge image. Also supports user to toggle HDR merge and toggle alternate show origin frame. | +| advanced | [multi_devices](3.advanced.multi_devices) | In this sample, users can connect multiple camera devices and get color and depth images of different cameras. | +| advanced | [multi_devices_sync](3.advanced.multi_devices_sync) | Users can connect multiple devices and use the SDK interface and configure json items for multi-devices synchronization operations. | +| advanced | [multi_devices_sync_gmsltrigger](3.advanced.multi_devices_sync_gmsltrigger) | gmsl device send pwm trigger,used together with the multi_devices_sync sample. +| advanced | [common_usages](3.advanced.common_usages) | Use SDK to handle the settings of device unplug callback and process the acquired code stream after unplugging. | +| misc | [logger](4.misc.logger) | Use the SDK interface to set the log output level and customize the output path. | +| misc | [metadata](4.misc.metadata) | In this sample, users can retrieve metadata for each stream of the camera. | + +### C language examples + +The listed examples at previous section are written in C++ language. Here is a brief introduction to c language examples: + +| Level | Sample | Description | +| ------ | ------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| basic | [c_quick_start](c_examples/0.c_quick_start) | This is a quick start guide to start device streams using the Orbbec SDK C API. | +| stream | [c_enumerate](c_examples/1.c_enumerate) | This is a enumerate guide to get device streams profile information using the Orbbec SDK C API. | +| stream | [c_depth](c_examples/2.c_depth) | This is a depth guide to get depth stream and depth image by using the Orbbec SDK C API. | + +## Append + +### The utils functions and classes used in the examples code + +***utils*** + +It contains functions to wait for a key press with an optional timeout, get the current time in milliseconds, and parse the input option from a key press event.This is done with C++. + +***utils_c*** + +It contains functions to get the current system timestamp and wait for keystrokes from the user, as well as a macro to check for and handle errors. These capabilities can be used in scenarios such as time measurement, user interaction, and error handling.This is done with C. + +***utils_opencv*** + +The CVWindow class includes the following main functionalities: + +The CVWindow class leverages OpenCV to create a flexible and customizable graphical interface for displaying and managing camera frames. + + *Window Creation:* + +- The constructor accepts the window name, width, and height as parameters, along with an optional - arrangement mode (ArrangeMode). +- The arrangement modes include single frame display, displaying multiple frames in a row, displaying multiple frames in a column, grid display, and overlay display. + + *Image Frame Display and Processing:* + +- The pushFramesToView method is used to push a set or multiple sets of image frames to the window. +- There is an internal thread processThread_ for processing image frames. +- The arrangeFrames method arranges the image frames based on the selected arrangement mode. +- The visualize and drawInfo methods are used to draw additional information on the images. + + *User Interaction:* + +- The setKeyPressedCallback method sets a key press callback function. +- The setKeyPrompt method sets a prompt message. +- The addLog method adds a log message. + + *Configuration and State Management:* + +- The setShowInfo method controls whether frame information should be displayed. +- The setAlpha set alpha for OVERLAY render mode +- The resize method adjusts the window size. +- The close method closes the window. +- The reset method clears the cached frames and image matrices. + +### The Error Handling in the examples code + +When using the Orbbec SDK, if an error occurs, the SDK reports the error by throwing an exception of type ob::Error. The ob::Error exception class typically contains detailed information about the error, which can help developers diagnose the problem. +The example uses a 'try' block to wrap the entire main function.If an exception of type ob::Error is thrown, the program will catch it and print the error message to the console. +Here is the information that can be obtained from an ob::Error: + +**Function Name (getFunction()):** +Indicates the name of the function where the exception was thrown. This helps pinpoint the location of the error within the code. + +**Arguments (getArgs()):** +Provides information about the arguments passed to the function when the exception occurred. This context can be useful for understanding the specific conditions under which the error happened. + +**Error Message (what()):** +Returns a string describing the nature of the error. This is often the most important piece of information, as it directly explains what went wrong. + +**Exception Type (getExceptionType()):** +Specifies the type of the exception. This can help categorize the error and determine appropriate handling strategies.Read the comments of the OBExceptionType enum in the libobsensor/h/ObTypes.h file for more information. + +**Example Code** in C++ + +```cpp +catch(ob::Error &e) { + std::cerr << "function: " << e.getFunction() << std::endl; + std::cerr << "args: " << e.getArgs() << std::endl; + std::cerr << "message: " << e.what() << std::endl; + std::cerr << "type: " << e.getExceptionType() << std::endl; + exit(EXIT_FAILURE); +} +``` diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/0.c_quick_start/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/0.c_quick_start/CMakeLists.txt new file mode 100644 index 0000000..fc72cd5 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/0.c_quick_start/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) + +project(ob_quick_start_c) +add_executable(${PROJECT_NAME} quick_start.c) + +target_link_libraries(${PROJECT_NAME} PRIVATE ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples_c") +if(MSVC) + set_target_properties(ob_quick_start_c PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ob_quick_start_c RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/0.c_quick_start/quick_start.c b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/0.c_quick_start/quick_start.c new file mode 100644 index 0000000..0890462 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/0.c_quick_start/quick_start.c @@ -0,0 +1,157 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include +#include +#include +#include + +#include "utils_c.h" +#include "utils_types.h" + +void calculate_and_print_frame_rate(const ob_frame *frameset) { + // Initialize variables + static float color_count = 0; + static float depth_count = 0; + static uint64_t color_timestamp_last = 0; + static uint64_t depth_timestamp_last = 0; + + ob_error *error = NULL; + const ob_frame *color_frame = NULL; + const ob_frame *depth_frame = NULL; + + if(color_timestamp_last == 0) { + color_timestamp_last = ob_smpl_get_current_timestamp_ms(); + } + if(depth_timestamp_last == 0) { + depth_timestamp_last = ob_smpl_get_current_timestamp_ms(); + } + + // Get color frame from frameset. + color_frame = ob_frameset_get_frame(frameset, OB_FRAME_COLOR, &error); + CHECK_OB_ERROR_EXIT(&error); + + // calculate and print color frame rate + if(color_frame != NULL) { + uint64_t color_timestamp_current; + uint64_t duration; + + color_count++; + // Get timestamp from color frame. + color_timestamp_current = ob_smpl_get_current_timestamp_ms(); + duration = color_timestamp_current - color_timestamp_last; + if(duration > 1000) { // calculate frame rate every second + uint64_t index; + uint32_t width; + uint32_t height; + double frame_rate; + + index = ob_frame_get_index(color_frame, &error); + CHECK_OB_ERROR_EXIT(&error); + + width = ob_video_frame_get_width(color_frame, &error); + CHECK_OB_ERROR_EXIT(&error); + + height = ob_video_frame_get_height(color_frame, &error); + CHECK_OB_ERROR_EXIT(&error); + + frame_rate = color_count / (duration / 1000.0); + + printf("Color frame index: %u, width: %u, height: %u, frame rate: %.2f\n", (uint32_t)index, width, height, frame_rate); + + color_count = 0; + color_timestamp_last = color_timestamp_current; + } + + // delete color frame + ob_delete_frame(color_frame, &error); + CHECK_OB_ERROR_EXIT(&error); + } + + // Get depth frame from frameset. + depth_frame = ob_frameset_get_frame(frameset, OB_FRAME_DEPTH, &error); + CHECK_OB_ERROR_EXIT(&error); + + // calculate and print depth frame rate + if(depth_frame != NULL) { + uint64_t depth_timestamp_current; + uint64_t duration; + + depth_count++; + // Get timestamp from depth frame. + depth_timestamp_current = ob_smpl_get_current_timestamp_ms(); + duration = depth_timestamp_current - depth_timestamp_last; + if(duration > 1000) { // 1 seconds + uint64_t index; + uint32_t width; + uint32_t height; + double frame_rate; + + index = ob_frame_get_index(depth_frame, &error); + CHECK_OB_ERROR_EXIT(&error); + + width = ob_video_frame_get_width(depth_frame, &error); + CHECK_OB_ERROR_EXIT(&error); + + height = ob_video_frame_get_height(depth_frame, &error); + CHECK_OB_ERROR_EXIT(&error); + + frame_rate = depth_count / (duration / 1000.0); + + printf("Depth frame index: %u, width: %u, height: %u, frame rate: %.2f\n", (uint32_t)index, width, height, frame_rate); + + depth_count = 0; + depth_timestamp_last = depth_timestamp_current; + } + + // delete depth frame + ob_delete_frame(depth_frame, &error); + CHECK_OB_ERROR_EXIT(&error); + } +} + +int main(void) { + // Used to return SDK interface error information. + ob_error *error = NULL; + + // Create a pipeline to manage the streams + ob_pipeline *pipe = ob_create_pipeline(&error); + CHECK_OB_ERROR_EXIT(&error); + + // Start Pipeline with default configuration (At default, the pipeline will start with the color and depth streams) + ob_pipeline_start(pipe, &error); + CHECK_OB_ERROR_EXIT(&error); + + printf("Streams have been started. Press 'ESC' key to stop the pipeline and exit the program.\n"); + + // Main loop, continuously wait for frames and print their index and rate. + while(true) { + ob_frame *frameset = NULL; + char key = ob_smpl_wait_for_key_press(1); + if(key == ESC_KEY) { + break; + } + + // Wait for frameset from pipeline, with a timeout of 1000 milliseconds. + frameset = ob_pipeline_wait_for_frameset(pipe, 1000, &error); + CHECK_OB_ERROR_EXIT(&error); + + // If frameset is NULL, timeout occurred, continue to next iteration. + if(frameset == NULL) { + continue; + } + + // Get the color and depth frames from the frameset and calculate their frame rate. + calculate_and_print_frame_rate(frameset); + + // delete frameset + ob_delete_frame(frameset, &error); + CHECK_OB_ERROR_EXIT(&error); + } + + // Stop Pipeline + ob_delete_pipeline(pipe, &error); + CHECK_OB_ERROR_EXIT(&error); + + return 0; +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/0.c_quick_start/readme.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/0.c_quick_start/readme.md new file mode 100644 index 0000000..d1f3e1e --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/0.c_quick_start/readme.md @@ -0,0 +1,64 @@ +# Quick Start with C + +This is a quick start guide to start device streams using the Orbbec SDK C API. + +## Overview + +### Knowledge + +**Pipeline** is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions. + +**Frameset** is a combination of different types of Frames + +## Code Overview + +### 1. Create pipeline + +```c +// Used to return SDK interface error information. +ob_error *error = NULL; + +// Create a pipeline to manage the streams +ob_pipeline *pipe = ob_create_pipeline(&error); +``` + +### 2. Start pipeline + +```c +// Start Pipeline with default configuration (At default, the pipeline will start with the color and depth streams) +ob_pipeline_start(pipe, &error); +``` + +### 3. Get frameset from pipeline + +```c +// Wait for frameset from pipeline, with a timeout of 1000 milliseconds. +ob_frame *frameset = ob_pipeline_wait_for_frameset(pipe, 1000, &error); +CHECK_OB_ERROR_EXIT(&error); + +// If frameset is NULL, timeout occurred, continue to next iteration. +if(frameset == NULL) { + continue; +} + +// Get the color and depth frames from the frameset and calculate their frame rate. +calculate_and_print_frame_rate(frameset); + +// delete frameset +ob_delete_frame(frameset, &error); +CHECK_OB_ERROR_EXIT(&error); +``` + +## Run Sample + +If you are on Windows, you can switch to the directory `OrbbecSDK-dev/build/win_XX/bin` to find the `ob_quick_start_c.exe`. + +If you are on linux, you can switch to the directory `OrbbecSDK-dev/build/linux_XX/bin` to find the `ob_quick_start_c`. + +### Key introduction + +Press 'ESC' key to stop the pipeline and exit the program. + +### Result + +![Quick_Start_C](../../../docs/resource/quick_start_c.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/1.c_enumerate/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/1.c_enumerate/CMakeLists.txt new file mode 100644 index 0000000..6bae50e --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/1.c_enumerate/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) + +project(ob_enumerate_c) +add_executable(${PROJECT_NAME} enumerate.c) + +target_link_libraries(${PROJECT_NAME} PRIVATE ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples_c") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/1.c_enumerate/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/1.c_enumerate/README.md new file mode 100644 index 0000000..c159fcd --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/1.c_enumerate/README.md @@ -0,0 +1,83 @@ +# Enumerate with C + +This is a enumerate guide to get device streams profile information using the Orbbec SDK C API. + +## Overview + +### Knowledge + +Context is the environment context, the first object created during initialization, which can be used to perform some settings, including but not limited to device status change callbacks, log level settings, etc. Context can access multiple Devices. + +## Code Overview + +### 1.Get inuput value + +```c +if(input == 'q' || input == 'Q') { + value = -1; + break; +} +if(input >= '0' && input <= '9' && input - '0' >= min_value && input - '0' <= max_value) { + value = input - '0'; + break; +} +printf("Invalid input, please input a number between %d and %d or \'q\' to exit program: ", min_value, max_value); +``` + +### 2.Enumerates stream information + +Get stream profile list, then different output formats are formulated according to sensor type. For example, if sensor type = 'OB_SENSOR_COLOR', need to print stream type、stream format、stream resolution、stream fps、stream index. + +```c +// Get sensor type. +ob_sensor_type sensor_type = ob_sensor_get_type(sensor, &error); +check_ob_error(&error); + +// Get stream profile list. +ob_stream_profile_list *stream_profile_list = ob_sensor_get_stream_profile_list(sensor, &error); +check_ob_error(&error); + +// Get stream profile count. +uint32_t stream_profile_count = ob_stream_profile_list_get_count(stream_profile_list, &error); +check_ob_error(&error); +``` + +### 3.Enumerate sensor list + +Get sensor list, then print sensor type. + +```c +// Get sensor list. +ob_sensor_list *sensor_list = ob_device_get_sensor_list(device, &error); +check_ob_error(&error); + +// Get sensor count. +uint32_t sensor_count = ob_sensor_list_get_count(sensor_list, &error); +check_ob_error(&error); +``` + +### 4.Enumerates device information + +Get device information. And then print device name、pid、SN、connect type. + +```c +// Get device information. +ob_device_info *dev_inf = ob_device_get_device_info(device, &error); +check_ob_error(&error); +``` + +## Run Sample + +If you are on Windows, you can switch to the directory `OrbbecSDK-dev/build/win_XX/bin` to find the `ob_enumerate_c.exe`. + +If you are on linux, you can switch to the directory `OrbbecSDK-dev/build/linux_XX/bin` to find the `ob_enumerate_c`. + +### Key introduction + + Input the devices index to get the sensor list. + Input 'q' to exit the program. + Input the sensor index to get the stream profile list. + +### Result + +![Enumerate_C](../../../docs/resource/enumerate.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/1.c_enumerate/enumerate.c b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/1.c_enumerate/enumerate.c new file mode 100644 index 0000000..39b5bcb --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/1.c_enumerate/enumerate.c @@ -0,0 +1,322 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils_c.h" + +#include +#include + +// helper function to check for errors and exit if there is one +void check_ob_error(ob_error **err) { + if(*err) { + const char *error_message = ob_error_get_message(*err); + fprintf(stderr, "Error: %s\n", error_message); + ob_delete_error(*err); + exit(-1); + } + *err = NULL; +} + +int select_index(const char *prompt, int min_value, int max_value) { // todo: to support select index > 9 + int value = 0; + printf("\n%s (Input device index or \'q\' to exit program): ", prompt); + while(true) { + char input; + int ret = scanf("%c", &input); + (void)ret; + getchar(); + + if(input == 'q' || input == 'Q') { + value = -1; + break; + } + if(input >= '0' && input <= '9' && input - '0' >= min_value && input - '0' <= max_value) { + value = input - '0'; + break; + } + printf("Invalid input, please input a number between %d and %d or \'q\' to exit program: ", min_value, max_value); + } + return value; +} + +// Enumerates stream information. +void enumerate_stream_info(ob_sensor *sensor) { + ob_error *error = NULL; +ob_stream_profile_list *stream_profile_list = NULL; +uint32_t stream_profile_count; + + // Get sensor type. + ob_sensor_type sensor_type = ob_sensor_get_type(sensor, &error); + check_ob_error(&error); + + // Get stream profile list. + stream_profile_list = ob_sensor_get_stream_profile_list(sensor, &error); + check_ob_error(&error); + + // Get stream profile count. + stream_profile_count = ob_stream_profile_list_get_count(stream_profile_list, &error); + check_ob_error(&error); + + printf("Available stream profiles: \n"); + for(uint32_t index = 0; index < stream_profile_count; index++) { + // Get stream profile. + const ob_stream_profile *stream_profile = ob_stream_profile_list_get_profile(stream_profile_list, index, &error); + check_ob_error(&error); + + // Print video stream profile information. + if(sensor_type == OB_SENSOR_IR || sensor_type == OB_SENSOR_COLOR || sensor_type == OB_SENSOR_DEPTH || sensor_type == OB_SENSOR_IR_LEFT + || sensor_type == OB_SENSOR_IR_RIGHT) { + ob_stream_type stream_type; + const char *stream_type_str; + const char *stream_format_str; + ob_format stream_format; + uint32_t stream_width; + uint32_t stream_height; + uint32_t stream_fps; + + stream_type = ob_stream_profile_get_type(stream_profile, &error); + check_ob_error(&error); + stream_type_str = ob_stream_type_to_string(stream_type); + + stream_format = ob_stream_profile_get_format(stream_profile, &error); + check_ob_error(&error); + stream_format_str = ob_format_to_string(stream_format); + + stream_width = ob_video_stream_profile_get_width(stream_profile, &error); + check_ob_error(&error); + + stream_height = ob_video_stream_profile_get_height(stream_profile, &error); + check_ob_error(&error); + + stream_fps = ob_video_stream_profile_get_fps(stream_profile, &error); + check_ob_error(&error); + + printf(" %u - type: %4s, format: %4s, width: %4u, height: %4u, fps: %4u\n", index, stream_type_str, stream_format_str, stream_width, stream_height, + stream_fps); + } + else if(sensor_type == OB_SENSOR_ACCEL) { + ob_format stream_format; + ob_accel_sample_rate acc_fps; + + // Print acc stream profile information. + stream_format = ob_stream_profile_get_format(stream_profile, &error); + check_ob_error(&error); + + acc_fps = ob_accel_stream_profile_get_sample_rate(stream_profile, &error); + check_ob_error(&error); + + printf(" %u - type: %s, fps: %s\n", index, ob_format_to_string(stream_format), ob_imu_rate_type_to_string(acc_fps)); + } + else if(sensor_type == OB_SENSOR_GYRO) { + ob_format stream_format; + ob_gyro_sample_rate gyro_fps; + + // Print gyro stream profile information. + stream_format = ob_stream_profile_get_format(stream_profile, &error); + check_ob_error(&error); + + gyro_fps = ob_gyro_stream_profile_get_sample_rate(stream_profile, &error); + check_ob_error(&error); + + printf(" %u - type: %s, fps: %s\n", index, ob_format_to_string(stream_format), ob_imu_rate_type_to_string(gyro_fps)); + } + + // destroy stream profile + ob_delete_stream_profile(stream_profile, &error); + check_ob_error(&error); + } + + // destroy stream profile list + ob_delete_stream_profile_list(stream_profile_list, &error); + check_ob_error(&error); +} + +// enumerate sensor list. +void enumerate_sensor_info(ob_device *device) { + ob_error *error = NULL; + uint32_t sensor_count; + + // Get sensor list. + ob_sensor_list *sensor_list = ob_device_get_sensor_list(device, &error); + check_ob_error(&error); + + // Get sensor count. + sensor_count = ob_sensor_list_get_count(sensor_list, &error); + check_ob_error(&error); + + while(true) { + int index; + + // Print sensor information. + printf("Available sensors: \n"); + for(uint32_t i = 0; i < sensor_count; i++) { + ob_sensor *sensor; + ob_sensor_type sensor_type; + const char *sensor_name; + + // Get device sensor. + sensor = ob_sensor_list_get_sensor(sensor_list, i, &error); + check_ob_error(&error); + + // Get sensor type. + sensor_type = ob_sensor_get_type(sensor, &error); + check_ob_error(&error); + sensor_name = ob_sensor_type_to_string(sensor_type); + + // Print sensor information. + printf(" %u - sensor name: %s\n", i, sensor_name); + + // destroy sensor + ob_delete_sensor(sensor, &error); + check_ob_error(&error); + } + + index = select_index("Select a sensor to enumerate its stream profiles", 0, sensor_count - 1); + if(index >= 0) { + // Get the selected sensor. + ob_sensor *sensor = ob_sensor_list_get_sensor(sensor_list, index, &error); + check_ob_error(&error); + + // Enumerate stream information of selected sensor. + enumerate_stream_info(sensor); + + // destroy sensor + ob_delete_sensor(sensor, &error); + check_ob_error(&error); + } + else { + break; + } + } + // destroy sensor list + ob_delete_sensor_list(sensor_list, &error); + check_ob_error(&error); +} + +// Enumerates device information. +void print_device_info(ob_device *device, int index) { + ob_error *error = NULL; + const char *dev_name = NULL; + int dev_pid; + const char *dev_sn = NULL; + const char *conn_type = NULL; + + // Get device information. + ob_device_info *dev_inf = ob_device_get_device_info(device, &error); + check_ob_error(&error); + + // Get device name. + dev_name = ob_device_info_get_name(dev_inf, &error); + check_ob_error(&error); + + // Get device pid. + dev_pid = ob_device_info_get_pid(dev_inf, &error); + check_ob_error(&error); + + // Get device serial number. + dev_sn = ob_device_info_get_serial_number(dev_inf, &error); + check_ob_error(&error); + + // Get connection type. + conn_type = ob_device_info_get_connection_type(dev_inf, &error); + check_ob_error(&error); + + printf(" %d - device name: %s, device pid: %#06x, device sn: %s, connection type: %s\n", index, dev_name, (unsigned int)dev_pid, dev_sn, conn_type); + + ob_delete_device_info(dev_inf, &error); + check_ob_error(&error); +} + +int main(void) { + + // Used to return SDK interface error information. + ob_error *error = NULL; + ob_context *ctx = NULL; + ob_device_list *dev_list = NULL; + uint32_t dev_count; + + // Get OrbbecSDK version. + int major_version = ob_get_major_version(); + int minor_version = ob_get_minor_version(); + int patch_version = ob_get_patch_version(); + printf("Orbbec SDK version: %d.%d.%d\n", major_version, minor_version, patch_version); + + // Create context. + ctx = ob_create_context(&error); + check_ob_error(&error); + + // Get device list from context. + dev_list = ob_query_device_list(ctx, &error); + check_ob_error(&error); + + // Get device count from device list. + dev_count = ob_device_list_get_count(dev_list, &error); + check_ob_error(&error); + + // Check if any device is connected. + if(dev_count == 0) { + printf("No device found! Please connect a supported device and retry this program.\n"); + + ob_delete_device_list(dev_list, &error); + check_ob_error(&error); + + ob_delete_context(ctx, &error); + check_ob_error(&error); + + printf("\nPress any key to exit."); + ob_smpl_wait_for_key_press(0); + + return -1; + } + + while(true) { + int device_index; + + printf("Connected devices: \n"); + for(uint32_t index = 0; index < dev_count; index++) { + // Get device from device list. + ob_device *dev = ob_device_list_get_device(dev_list, index, &error); + check_ob_error(&error); + + // print device information + print_device_info(dev, index); + + // destroy device + ob_delete_device(dev, &error); + check_ob_error(&error); + } + + // Select a device. + device_index = select_index("Select a device to enumerate its sensors", 0, dev_count - 1); + if(device_index >= 0) { + // get device from device list + ob_device *device = ob_device_list_get_device(dev_list, device_index, &error); + check_ob_error(&error); + + // enumerate sensors of device + enumerate_sensor_info(device); + + // destroy device + ob_delete_device(device, &error); + check_ob_error(&error); + } + else { + break; + } + } + + // destroy sensor list + ob_delete_device_list(dev_list, &error); + check_ob_error(&error); + + // destroy context + ob_delete_context(ctx, &error); + check_ob_error(&error); + + printf("\nProgram ended successfully. Press any key to exit."); + ob_smpl_wait_for_key_press(0); + + return 0; +} diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/2.c_depth/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/2.c_depth/CMakeLists.txt new file mode 100644 index 0000000..ca7c663 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/2.c_depth/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) + +project(ob_depth_c) +add_executable(${PROJECT_NAME} depth.c) + +target_link_libraries(${PROJECT_NAME} PRIVATE ob::OrbbecSDK ob::examples::utils) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "examples_c") +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/2.c_depth/README.md b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/2.c_depth/README.md new file mode 100644 index 0000000..4af6ac7 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/2.c_depth/README.md @@ -0,0 +1,115 @@ +# Depth with C + +This is a depth guide to get depth stream and depth image by using the Orbbec SDK C API. + +## Overview + +### Knowledge + +**Pipeline** is a pipeline for processing data streams, providing multi-channel stream configuration, switching, frame aggregation, and frame synchronization functions. + +**Frameset** is a combination of different types of Frames. + +**Depth_frame** is one of the frames in frameset, it contains the frames's esolution information and depth data. + +## Code Overview + +### 1. Create instances + +Create the pipeline instance using the default configuration and create a config instance to enable or disable the streams. + +```c +// Used to return SDK interface error information. +ob_error *error = NULL; + +// Create a pipeline object to manage the streams. +ob_pipeline *pipeline = ob_create_pipeline(&error); +check_ob_error(&error); + +// Crete a config object to configure the pipeline streams. +ob_config *config = ob_create_config(&error); +check_ob_error(&error); +``` + +### 2. Start pipeline + +enable the depth stream, and then start the pipeline. + +```c +// enable depth stream with default stream profile. +ob_config_enable_stream(config, OB_STREAM_DEPTH, &error); +check_ob_error(&error); + +// Start Pipeline with the configured streams. +ob_pipeline_start_with_config(pipeline, config, &error); +check_ob_error(&error); +``` + +### 3. Get depth stream + +Get the frameset from pipeline, and get the depth frame from frameset. + +```c +// Wait for a frameset, timeout after 1000 milliseconds. +const ob_frame *frameset = ob_pipeline_wait_for_frameset(pipeline, 1000, &error); +check_ob_error(&error); + +// If no frameset is available with in the timeout, continue waiting. +if(frameset == NULL) { + continue; +} + +// Get the depth frame from frameset +const ob_frame *depth_frame = ob_frameset_get_depth_frame(frameset, &error); +check_ob_error(&error); +``` + +### 4. Print the distance of the center pixel + +Print the distance of the center pixel every 30 frames to reduce output. + +```c +if(index % 30 == 0) { + // Get the width of the depth frame. + uint32_t width = ob_video_frame_get_width(depth_frame, &error); + check_ob_error(&error); + + // Get the height of the depth frame. + uint32_t height = ob_video_frame_get_height(depth_frame, &error); + check_ob_error(&error); + + // Get the scale of the depth frame. + float scale = ob_depth_frame_get_value_scale(depth_frame, &error); + check_ob_error(&error); + + // Get the data of the depth frame, cast to uint16_t* to access the pixels directly for Y16 format. + uint16_t *data = (uint16_t *)ob_frame_get_data(depth_frame, &error); + check_ob_error(&error); + + // pixel value multiplied by scale is the actual distance value in millimeters + float center_distance = data[width * height / 2 + width / 2] * scale; + + // attention: if the distance is 0, it means that the depth camera cannot detect the object (may be out of detection range) + printf("Facing an object at a distance of %.3f mm away.\n", center_distance); +} +``` + +### 5. Stop pipeline + +```c +ob_pipeline_stop(pipeline, &error); +``` + +## Run Sample + +If you are on Windows, you can switch to the directory `OrbbecSDK-dev/build/win_XX/bin` to find the `ob_depth_c.exe`. + +If you are on linux, you can switch to the directory `OrbbecSDK-dev/build/linux_XX/bin` to find the `ob_depth_c`. + +### Key introduction + +Press 'ESC' key to stop the pipeline and exit the program. + +### Result + +![Quick_Start_C](../../../docs/resource/quick_start_c.jpg) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/2.c_depth/depth.c b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/2.c_depth/depth.c new file mode 100644 index 0000000..c46ba05 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/2.c_depth/depth.c @@ -0,0 +1,128 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include + +#include "utils_c.h" +#include "utils_types.h" + +#include +#include + +// helper function to check for errors and exit if there is one +void check_ob_error(ob_error **err) { + if(*err) { + const char *error_message = ob_error_get_message(*err); + fprintf(stderr, "Error: %s\n", error_message); + ob_delete_error(*err); + exit(-1); + } + *err = NULL; +} + +int main(void) { + // Used to return SDK interface error information. + ob_error *error = NULL; + ob_config *config = NULL; + + // Create a pipeline object to manage the streams. + ob_pipeline *pipeline = ob_create_pipeline(&error); + check_ob_error(&error); + + // Crete a config object to configure the pipeline streams. + config = ob_create_config(&error); + check_ob_error(&error); + + // enable depth stream with default stream profile. + ob_config_enable_stream(config, OB_STREAM_DEPTH, &error); + check_ob_error(&error); + + // Start Pipeline with the configured streams. + ob_pipeline_start_with_config(pipeline, config, &error); + check_ob_error(&error); + + printf("Depth Stream Started. Press 'ESC' key to exit the program.\n"); + + // Wait frameset in a loop, exit when ESC is pressed. + while(true) { + const ob_frame *frameset = NULL; +const ob_frame *depth_frame = NULL; +uint64_t index; + + // Wait for a key press + char key = ob_smpl_wait_for_key_press(10); + if(key == ESC_KEY) { // ESC key + printf("Exiting...\n"); + break; + } + + // Wait for a frameset, timeout after 1000 milliseconds. + frameset = ob_pipeline_wait_for_frameset(pipeline, 1000, &error); + check_ob_error(&error); + + // If no frameset is available with in the timeout, continue waiting. + if(frameset == NULL) { + continue; + } + + // Get the depth frame from frameset. + depth_frame = ob_frameset_get_depth_frame(frameset, &error); + check_ob_error(&error); + + // Get index from depth frame. + index = ob_frame_get_index(depth_frame, &error); + check_ob_error(&error); + + // print the distance of the center pixel every 30 frames to reduce output + if(index % 30 == 0) { + uint32_t width; + uint32_t height; + float scale; + uint16_t *data; + float center_distance; + + // Get the width and height of the depth frame. + width = ob_video_frame_get_width(depth_frame, &error); + check_ob_error(&error); + + // Get the height of the depth frame. + height = ob_video_frame_get_height(depth_frame, &error); + check_ob_error(&error); + + // Get the scale of the depth frame. + scale = ob_depth_frame_get_value_scale(depth_frame, &error); + check_ob_error(&error); + + // Get the data of the depth frame, cast to uint16_t* to access the pixels directly for Y16 format. + data = (uint16_t *)ob_frame_get_data(depth_frame, &error); + check_ob_error(&error); + + // pixel value multiplied by scale is the actual distance value in millimeters + center_distance = data[width * height / 2 + width / 2] * scale; + + // attention: if the distance is 0, it means that the depth camera cannot detect the object (may be out of detection range) + printf("Facing an object at a distance of %.3f mm away.\n", center_distance); + } + + // delete the depth frame + ob_delete_frame(depth_frame, &error); + check_ob_error(&error); + + // delete the frameset + ob_delete_frame(frameset, &error); + check_ob_error(&error); + }; + + // stop the pipeline + ob_pipeline_stop(pipeline, &error); + check_ob_error(&error); + + ob_delete_config(config, &error); + check_ob_error(&error); + + ob_delete_pipeline(pipeline, &error); + check_ob_error(&error); + + return 0; +} + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/CMakeLists.txt new file mode 100644 index 0000000..5d9d5a7 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/c_examples/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) + +file(GLOB subdirectories RELATIVE ${CMAKE_CURRENT_LIST_DIR} "*") + +foreach(subdir ${subdirectories}) + if(IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/${subdir}) + add_subdirectory(${subdir}) + endif() +endforeach() + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/CMakeLists.txt b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/CMakeLists.txt new file mode 100644 index 0000000..67791ce --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.5) +project(ob_examples_utils) + +add_library(ob_examples_utils STATIC + ${CMAKE_CURRENT_LIST_DIR}/utils_c.c + ${CMAKE_CURRENT_LIST_DIR}/utils_c.h + ${CMAKE_CURRENT_LIST_DIR}/utils.cpp + ${CMAKE_CURRENT_LIST_DIR}/utils.hpp +) + +if(${OpenCV_FOUND}) + target_sources(ob_examples_utils PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/utils_opencv.cpp + ${CMAKE_CURRENT_LIST_DIR}/utils_opencv.hpp + ) + target_link_libraries(ob_examples_utils PUBLIC ${OpenCV_LIBS} ob::OrbbecSDK) + target_include_directories(ob_examples_utils PUBLIC ${OpenCV_INCLUDE_DIRS}) +endif() + +find_package(Threads REQUIRED) +target_link_libraries(ob_examples_utils PUBLIC Threads::Threads) + +target_include_directories(ob_examples_utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(ob::examples::utils ALIAS ob_examples_utils) +set_target_properties(ob_examples_utils PROPERTIES FOLDER "examples") diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils.cpp new file mode 100644 index 0000000..b1a4a05 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils.cpp @@ -0,0 +1,26 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include "utils.hpp" +#include "utils_c.h" + +#include + +namespace ob_smpl { +char waitForKeyPressed(uint32_t timeout_ms) { + return ob_smpl_wait_for_key_press(timeout_ms); +} + +uint64_t getNowTimesMs() { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +int getInputOption() { + char inputOption = ob_smpl::waitForKeyPressed(); + if(inputOption == ESC_KEY) { + return -1; + } + return inputOption - '0'; +} + +} // namespace ob_smpl diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils.hpp new file mode 100644 index 0000000..b8059a2 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils.hpp @@ -0,0 +1,23 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#pragma once +#include +#include "utils_types.h" + +#include + +namespace ob_smpl { +char waitForKeyPressed(uint32_t timeout_ms = 0); + +uint64_t getNowTimesMs(); + +int getInputOption(); + +template std::string toString(const T a_value, const int n = 6) { + std::ostringstream out; + out.precision(n); + out << std::fixed << a_value; + return std::move(out).str(); +} +} // namespace ob_smpl diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_c.c b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_c.c new file mode 100644 index 0000000..b803eae --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_c.c @@ -0,0 +1,141 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include "utils_c.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__linux__) || defined(__APPLE__) +#ifdef __linux__ +#include +#else +#include +#endif +#include +#include +#include +#include + +#define gets_s gets + +int getch(void) { + struct termios tm, tm_old; + int fd = 0, ch; + + if(tcgetattr(fd, &tm) < 0) { // Save the current terminal settings + return -1; + } + + tm_old = tm; + cfmakeraw(&tm); // Change the terminal settings to raw mode, in which all input data is processed in bytes + if(tcsetattr(fd, TCSANOW, &tm) < 0) { // Settings after changes on settings + return -1; + } + + ch = getchar(); + if(tcsetattr(fd, TCSANOW, &tm_old) < 0) { // Change the settings to what they were originally + return -1; + } + + return ch; +} + +int kbhit(void) { + struct termios oldt, newt; + int ch; + int oldf; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + oldf = fcntl(STDIN_FILENO, F_GETFL, 0); + fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); + ch = getchar(); + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + fcntl(STDIN_FILENO, F_SETFL, oldf); + if(ch != EOF) { + ungetc(ch, stdin); + return 1; + } + return 0; +} + +#include +uint64_t ob_smpl_get_current_timestamp_ms(void) { + struct timeval te; + long long milliseconds; + gettimeofday(&te, NULL); // Get the current time + milliseconds = te.tv_sec * 1000LL + te.tv_usec / 1000; // Calculate milliseconds + return milliseconds; +} + +char ob_smpl_wait_for_key_press(uint32_t timeout_ms) { // Get the current time + struct timeval te; + long long start_time; + gettimeofday(&te, NULL); + start_time = te.tv_sec * 1000LL + te.tv_usec / 1000; + + while(true) { + long long current_time; + if(kbhit()) { + return getch(); + } + gettimeofday(&te, NULL); + current_time = te.tv_sec * 1000LL + te.tv_usec / 1000; + if(timeout_ms > 0 && current_time - start_time > timeout_ms) { + return 0; + } + usleep(100); + } +} + +#else // Windows +#include +#include + +uint64_t ob_smpl_get_current_timestamp_ms() { + FILETIME ft; + LARGE_INTEGER li; + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + long long milliseconds = li.QuadPart / 10000LL; + return milliseconds; +} + +char ob_smpl_wait_for_key_press(uint32_t timeout_ms) { + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if(hStdin == INVALID_HANDLE_VALUE) { + return 0; + } + DWORD mode = 0; + if(!GetConsoleMode(hStdin, &mode)) { + return 0; + } + mode &= ~ENABLE_ECHO_INPUT; + if(!SetConsoleMode(hStdin, mode)) { + return 0; + } + DWORD start_time = GetTickCount(); + while(true) { + if(_kbhit()) { + char ch = (char)_getch(); + SetConsoleMode(hStdin, mode); + return ch; + } + if(timeout_ms > 0 && GetTickCount() - start_time > timeout_ms) { + SetConsoleMode(hStdin, mode); + return 0; + } + Sleep(1); + } +} +#endif + +#ifdef __cplusplus +} +#endif + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_c.h b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_c.h new file mode 100644 index 0000000..b5be0c8 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_c.h @@ -0,0 +1,40 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get the current system timestamp in milliseconds. + * + */ +uint64_t ob_smpl_get_current_timestamp_ms(void); + +/** + * @brief Wait for key press. + * + * @param[in] timeout_ms The maximum time to wait for a key press in milliseconds. Set to 0 to wait indefinitely. + * + * @return char The key that was pressed. + */ +char ob_smpl_wait_for_key_press(uint32_t timeout_ms); + +// Macro to check for error and exit program if there is one. +#define CHECK_OB_ERROR_EXIT(error) \ + if(*error) { \ + const char *error_message = ob_error_get_message(*error); \ + fprintf(stderr, "Error: %s\n", error_message); \ + ob_delete_error(*error); \ + *error = NULL; \ + exit(-1); \ + } \ + *error = NULL; + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_opencv.cpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_opencv.cpp new file mode 100644 index 0000000..52efae2 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_opencv.cpp @@ -0,0 +1,598 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#include "utils_opencv.hpp" +#include "utils.hpp" +#include "utils_types.h" + +#if defined(__has_include) +#if __has_include() +#include +#define TO_DISABLE_OPENCV_LOG +#endif +#endif + +namespace ob_smpl { + +const std::string defaultKeyMapPrompt = "'Esc': Exit Window, '?': Show Key Map"; +CVWindow::CVWindow(std::string name, uint32_t width, uint32_t height, ArrangeMode arrangeMode) + : name_(std::move(name)), + arrangeMode_(arrangeMode), + width_(width), + height_(height), + closed_(false), + showInfo_(true), + showSyncTimeInfo_(false), + isWindowDestroyed_(false), + alpha_(0.6f), + showPrompt_(false) { + +#if defined(TO_DISABLE_OPENCV_LOG) + cv::utils::logging::setLogLevel(cv::utils::logging::LogLevel::LOG_LEVEL_SILENT); +#endif + + prompt_ = defaultKeyMapPrompt; + + cv::namedWindow(name_, cv::WINDOW_NORMAL); + cv::resizeWindow(name_, width_, height_); + + renderMat_ = cv::Mat::zeros(height_, width_, CV_8UC3); + cv::putText(renderMat_, "Waiting for streams...", cv::Point(8, 16), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + cv::imshow(name_, renderMat_); + + // start processing thread + processThread_ = std::thread(&CVWindow::processFrames, this); + + winCreatedTime_ = getNowTimesMs(); +} + +CVWindow::~CVWindow() noexcept { + close(); + destroyWindow(); +} + +void CVWindow::setKeyPressedCallback(std::function callback) { + keyPressedCallback_ = callback; +} + +// if window is closed +bool CVWindow::run() { + + { + // show render mat + std::lock_guard lock(renderMatsMtx_); + cv::imshow(name_, renderMat_); + } + + int key = cv::waitKey(1); + if(key != -1) { + if(key == ESC_KEY) { + closed_ = true; + srcFrameGroupsCv_.notify_all(); + } + else if(key == '1') { + arrangeMode_ = ARRANGE_SINGLE; + addLog("Switch to SINGLE arrange mode"); + } + else if(key == '2') { + arrangeMode_ = ARRANGE_ONE_ROW; + addLog("Switch to ONE_ROW arrange mode"); + } + else if(key == '3') { + arrangeMode_ = ARRANGE_ONE_COLUMN; + addLog("Switch to ONE_COLUMN arrange mode"); + } + else if(key == '4') { + arrangeMode_ = ARRANGE_GRID; + addLog("Switch to GRID arrange mode"); + } + else if(key == '5') { + arrangeMode_ = ARRANGE_OVERLAY; + addLog("Switch to OVERLAY arrange mode"); + } + else if(key == '?' || key == '/') { + showPrompt_ = !showPrompt_; + } + else if(key == '+' || key == '=') { + alpha_ += 0.1f; + if(alpha_ > 1) { + alpha_ = 1; + } + addLog("Adjust alpha to " + ob_smpl::toString(alpha_, 1) + " (Only valid in OVERLAY arrange mode)"); + } + else if(key == '-' || key == '_') { + alpha_ -= 0.1f; + if(alpha_ < 0) { + alpha_ = 0; + } + addLog("Adjust alpha to " + ob_smpl::toString(alpha_, 1) + " (Only valid in OVERLAY arrange mode)"); + } + if(keyPressedCallback_) { + keyPressedCallback_(key); + } + } + return !closed_; +} + +// close window +void CVWindow::close() { + { + std::lock_guard lock(renderMatsMtx_); + closed_ = true; + srcFrameGroupsCv_.notify_all(); + } + + if(processThread_.joinable()) { + processThread_.join(); + } + + matGroups_.clear(); + srcFrameGroups_.clear(); +} + +void CVWindow::destroyWindow() { + if(!isWindowDestroyed_) { + cv::destroyWindow(name_); + cv::waitKey(1); + isWindowDestroyed_ = true; + } + else { + std::cout << "CVWindows has been destroyed!" << std::endl; + } +} + +void CVWindow::reset() { + // close thread and clear cache + close(); + + // restart thread + closed_ = false; + processThread_ = std::thread(&CVWindow::processFrames, this); +} + +// set the window size +void CVWindow::resize(int width, int height) { + width_ = width; + height_ = height; + cv::resizeWindow(name_, width_, height_); +} + +void CVWindow::setKeyPrompt(const std::string &prompt) { + prompt_ = defaultKeyMapPrompt + ", " + prompt; +} + +void CVWindow::addLog(const std::string &log) { + log_ = log; + logCreatedTime_ = getNowTimesMs(); +} + +// add frames to the show +void CVWindow::pushFramesToView(std::vector> frames, int groupId) { + if(frames.empty()) { + return; + } + + std::vector> singleFrames; + for(auto &frame: frames) { + if(frame == nullptr) { + continue; + } + + if(!frame->is()) { + // single frame, add to the list + singleFrames.push_back(frame); + continue; + } + + // FrameSet contains multiple frames + auto frameSet = frame->as(); + for(uint32_t index = 0; index < frameSet->getCount(); index++) { + auto subFrame = frameSet->getFrameByIndex(index); + singleFrames.push_back(subFrame); + } + } + + std::lock_guard lk(srcFrameGroupsMtx_); + srcFrameGroups_[groupId] = singleFrames; + srcFrameGroupsCv_.notify_one(); +} + +void CVWindow::pushFramesToView(std::shared_ptr currentFrame, int groupId) { + pushFramesToView(std::vector>{ currentFrame }, groupId); +} + +// set show frame info +void CVWindow::setShowInfo(bool show) { + showInfo_ = show; +} + +// set show frame synctime info +void CVWindow::setShowSyncTimeInfo(bool show) { + showSyncTimeInfo_ = show; +} +// set alpha for OVERLAY render mode +void CVWindow::setAlpha(float alpha) { + alpha_ = alpha; + if(alpha_ < 0) { + alpha_ = 0; + } + else if(alpha_ > 1) { + alpha_ = 1; + } +} + +// frames processing thread +void CVWindow::processFrames() { + std::map>> frameGroups; + while(!closed_) { + if(closed_) { + break; + } + { + std::unique_lock lk(srcFrameGroupsMtx_); + srcFrameGroupsCv_.wait(lk); + frameGroups = srcFrameGroups_; + } + + if(frameGroups.empty()) { + continue; + } + + for(const auto &framesItem: frameGroups) { + int groupId = framesItem.first; + const auto &frames = framesItem.second; + for(const auto &frame: frames) { + auto rstMat = visualize(frame); + if(!rstMat.empty()) { + int uid = groupId * OB_FRAME_TYPE_COUNT + static_cast(frame->getType()); + matGroups_[uid] = { frame, rstMat }; + } + } + } + + if(matGroups_.empty()) { + continue; + } + + arrangeFrames(); + } +} + +void CVWindow::arrangeFrames() { + cv::Mat renderMat; + try { + if(arrangeMode_ == ARRANGE_SINGLE || matGroups_.size() == 1) { + auto &mat = matGroups_.begin()->second.second; + renderMat = resizeMatKeepAspectRatio(mat, width_, height_); + } + else if(arrangeMode_ == ARRANGE_ONE_ROW) { + for(auto &item: matGroups_) { + auto &mat = item.second.second; + cv::Mat resizeMat = resizeMatKeepAspectRatio(mat, static_cast(width_ / matGroups_.size()), height_); + if(renderMat.dims > 0 && renderMat.cols > 0 && renderMat.rows > 0) { + cv::hconcat(renderMat, resizeMat, renderMat); + } + else { + renderMat = resizeMat; + } + } + } + else if(arrangeMode_ == ARRANGE_ONE_COLUMN) { + for(auto &item: matGroups_) { + auto &mat = item.second.second; + cv::Mat resizeMat = resizeMatKeepAspectRatio(mat, width_, static_cast(height_ / matGroups_.size())); + if(renderMat.dims > 0 && renderMat.cols > 0 && renderMat.rows > 0) { + cv::vconcat(renderMat, resizeMat, renderMat); + } + else { + renderMat = resizeMat; + } + } + } + else if(arrangeMode_ == ARRANGE_GRID) { + int count = static_cast(matGroups_.size()); + int idealSide = static_cast(std::sqrt(count)); + int rows = idealSide; + int cols = idealSide; + while(rows * cols < count) { // find the best row and column count + cols++; + if(rows * cols < count) { + rows++; + } + } + + std::vector gridImages; // store all images in grid + auto it = matGroups_.begin(); + for(int i = 0; i < rows; i++) { + std::vector rowImages; // store images in the same row + for(int j = 0; j < cols; j++) { + int index = i * cols + j; + cv::Mat resizeMat; + if(index < count) { + auto mat = it->second.second; + resizeMat = resizeMatKeepAspectRatio(mat, width_ / cols, height_ / rows); + it++; + } + else { + resizeMat = cv::Mat::zeros(height_ / rows, width_ / cols, CV_8UC3); // fill with black + } + rowImages.push_back(resizeMat); + } + cv::Mat lineMat; + cv::hconcat(rowImages, lineMat); // horizontal concat all images in the same row + gridImages.push_back(lineMat); + } + + cv::vconcat(gridImages, renderMat); // vertical concat all images in the grid + } + else if(arrangeMode_ == ARRANGE_OVERLAY && matGroups_.size() >= 2) { + cv::Mat overlayMat; + const auto &mat1 = matGroups_.begin()->second.second; + const auto &mat2 = matGroups_.rbegin()->second.second; + renderMat = resizeMatKeepAspectRatio(mat1, width_, height_); + overlayMat = resizeMatKeepAspectRatio(mat2, width_, height_); + + float alpha = alpha_; + for(int i = 0; i < renderMat.rows; i++) { + for(int j = 0; j < renderMat.cols; j++) { + cv::Vec3b &outRgb = renderMat.at(i, j); + cv::Vec3b &overlayRgb = overlayMat.at(i, j); + + outRgb[0] = (uint8_t)(outRgb[0] * (1.0f - alpha) + overlayRgb[0] * alpha); + outRgb[1] = (uint8_t)(outRgb[1] * (1.0f - alpha) + overlayRgb[1] * alpha); + outRgb[2] = (uint8_t)(outRgb[2] * (1.0f - alpha) + overlayRgb[2] * alpha); + } + } + } + } + catch(std::exception &e) { + std::cerr << e.what() << std::endl; + } + + if(renderMat.empty()) { + return; + } + + if(showPrompt_ || getNowTimesMs() - winCreatedTime_ < 5000) { + cv::putText(renderMat, prompt_, cv::Point(8, 16), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + } + + if(!log_.empty() && getNowTimesMs() - logCreatedTime_ < 3000) { + cv::putText(renderMat, log_, cv::Point(8, height_ - 16), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + } + + std::lock_guard lock(renderMatsMtx_); + renderMat_ = renderMat; +} + +cv::Mat CVWindow::visualize(std::shared_ptr frame) { + if(frame == nullptr) { + return cv::Mat(); + } + + cv::Mat rstMat; + if(frame->getType() == OB_FRAME_COLOR) { + auto videoFrame = frame->as(); + switch(videoFrame->getFormat()) { + case OB_FORMAT_MJPG: { + cv::Mat rawMat(1, videoFrame->getDataSize(), CV_8UC1, videoFrame->getData()); + rstMat = cv::imdecode(rawMat, 1); + } break; + case OB_FORMAT_NV21: { + cv::Mat rawMat(videoFrame->getHeight() * 3 / 2, videoFrame->getWidth(), CV_8UC1, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_YUV2BGR_NV21); + } break; + case OB_FORMAT_YUYV: + case OB_FORMAT_YUY2: { + cv::Mat rawMat(videoFrame->getHeight(), videoFrame->getWidth(), CV_8UC2, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_YUV2BGR_YUY2); + } break; + case OB_FORMAT_BGR: { + cv::Mat rawMat(videoFrame->getHeight(), videoFrame->getWidth(), CV_8UC3, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_BGR2RGB); + } break; + case OB_FORMAT_RGB: { + cv::Mat rawMat(videoFrame->getHeight(), videoFrame->getWidth(), CV_8UC3, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_RGB2BGR); + } break; + case OB_FORMAT_RGBA: { + cv::Mat rawMat(videoFrame->getHeight(), videoFrame->getWidth(), CV_8UC4, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_RGBA2BGR); + } break; + case OB_FORMAT_BGRA: { + cv::Mat rawMat(videoFrame->getHeight(), videoFrame->getWidth(), CV_8UC4, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_BGRA2RGB); + } break; + case OB_FORMAT_UYVY: { + cv::Mat rawMat(videoFrame->getHeight(), videoFrame->getWidth(), CV_8UC2, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_YUV2BGR_UYVY); + } break; + case OB_FORMAT_I420: { + cv::Mat rawMat(videoFrame->getHeight() * 3 / 2, videoFrame->getWidth(), CV_8UC1, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_YUV2BGR_I420); + } break; + case OB_FORMAT_Y8: { + cv::Mat rawMat(videoFrame->getHeight(), videoFrame->getWidth(), CV_8UC1, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_GRAY2BGR); + } break; + case OB_FORMAT_Y16: { + cv::Mat rawMat(videoFrame->getHeight(), videoFrame->getWidth(), CV_16UC1, videoFrame->getData()); + cv::Mat gray8; + rawMat.convertTo(gray8, CV_8UC1, 255.0 / 65535.0); + cv::cvtColor(gray8, rstMat, cv::COLOR_GRAY2BGR); + } break; + default: + break; + } + if(showSyncTimeInfo_ && !rstMat.empty()) { + drawInfo(rstMat, videoFrame); + } + } + else if(frame->getType() == OB_FRAME_DEPTH) { + auto videoFrame = frame->as(); + if(videoFrame->getFormat() == OB_FORMAT_Y16 || videoFrame->getFormat() == OB_FORMAT_Z16 || videoFrame->getFormat() == OB_FORMAT_Y12C4) { + cv::Mat rawMat = cv::Mat(videoFrame->getHeight(), videoFrame->getWidth(), CV_16UC1, videoFrame->getData()); + // depth frame pixel value multiply scale to get distance in millimeter + float scale = videoFrame->as()->getValueScale(); + + cv::Mat cvtMat; + // normalization to 0-255. 0.032f is 256/8000, to limit the range of depth to 8000mm + rawMat.convertTo(cvtMat, CV_32F, scale * 0.032f); + + // apply gamma correction to enhance the contrast for near objects + cv::pow(cvtMat, 0.6f, cvtMat); + + // convert to 8-bit + cvtMat.convertTo(cvtMat, CV_8UC1, 10); // multiplier 10 is to normalize to 0-255 (nearly) after applying gamma correction + + // apply colormap + cv::applyColorMap(cvtMat, rstMat, cv::COLORMAP_JET); + } + if(showSyncTimeInfo_ && !rstMat.empty()) { + drawInfo(rstMat, videoFrame); + } + } + else if(frame->getType() == OB_FRAME_IR || frame->getType() == OB_FRAME_IR_LEFT || frame->getType() == OB_FRAME_IR_RIGHT) { + auto videoFrame = frame->as(); + if(videoFrame->getFormat() == OB_FORMAT_Y16) { + cv::Mat cvtMat; + cv::Mat rawMat = cv::Mat(videoFrame->getHeight(), videoFrame->getWidth(), CV_16UC1, videoFrame->getData()); + rawMat.convertTo(cvtMat, CV_8UC1, 1.0 / 16.0f); + cv::cvtColor(cvtMat, rstMat, cv::COLOR_GRAY2RGB); + } + else if(videoFrame->getFormat() == OB_FORMAT_Y8) { + cv::Mat rawMat = cv::Mat(videoFrame->getHeight(), videoFrame->getWidth(), CV_8UC1, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_GRAY2RGB); + } + else if(videoFrame->getFormat() == OB_FORMAT_MJPG) { + cv::Mat rawMat(1, videoFrame->getDataSize(), CV_8UC1, videoFrame->getData()); + rstMat = cv::imdecode(rawMat, 1); + cv::cvtColor(rstMat, rstMat, cv::COLOR_GRAY2RGB); + } + if(showSyncTimeInfo_ && !rstMat.empty()) { + drawInfo(rstMat, videoFrame); + } + } + else if(frame->getType() == OB_FRAME_CONFIDENCE) { + auto videoFrame = frame->as(); + if(videoFrame->getFormat() == OB_FORMAT_Y16) { + cv::Mat cvtMat; + cv::Mat rawMat = cv::Mat(videoFrame->getHeight(), videoFrame->getWidth(), CV_16UC1, videoFrame->getData()); + rawMat.convertTo(cvtMat, CV_8UC1, 1.0 / 16.0f); + cv::cvtColor(cvtMat, rstMat, cv::COLOR_GRAY2RGB); + } + else if(videoFrame->getFormat() == OB_FORMAT_Y8) { + cv::Mat rawMat = cv::Mat(videoFrame->getHeight(), videoFrame->getWidth(), CV_8UC1, videoFrame->getData()); + cv::cvtColor(rawMat, rstMat, cv::COLOR_GRAY2RGB); + } + } + else if(frame->getType() == OB_FRAME_ACCEL) { + rstMat = cv::Mat::zeros(320, 300, CV_8UC3); + auto accelFrame = frame->as(); + auto value = accelFrame->getValue(); + std::string str = "Accel:"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 60), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + str = std::string(" timestamp=") + std::to_string(accelFrame->getTimeStampUs()) + "us"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 100), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + str = std::string(" x=") + std::to_string(value.x) + "m/s^2"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 140), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + str = std::string(" y=") + std::to_string(value.y) + "m/s^2"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 180), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + str = std::string(" z=") + std::to_string(value.z) + "m/s^2"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 220), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + } + else if(frame->getType() == OB_FRAME_GYRO) { + rstMat = cv::Mat::zeros(320, 300, CV_8UC3); + auto gyroFrame = frame->as(); + auto value = gyroFrame->getValue(); + std::string str = "Gyro:"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 60), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + str = std::string(" timestamp=") + std::to_string(gyroFrame->getTimeStampUs()) + "us"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 100), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + str = std::string(" x=") + std::to_string(value.x) + "rad/s"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 140), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + str = std::string(" y=") + std::to_string(value.y) + "rad/s"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 180), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + str = std::string(" z=") + std::to_string(value.z) + "rad/s"; + cv::putText(rstMat, str.c_str(), cv::Point(8, 220), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(255, 255, 255), 1, cv::LINE_AA); + } + return rstMat; +} + +// add frame info to mat +void CVWindow::drawInfo(cv::Mat &imageMat, std::shared_ptr &frame) { + int baseline = 0; // Used to calculate text size and baseline + cv::Size textSize; // Size of the text to be drawn + int padding = 5; // Padding around the text for the background + + // Helper lambda function to draw text with background + auto putTextWithBackground = [&](const std::string &text, cv::Point origin) { + // Getting text size for background + textSize = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.6, 1, &baseline); + + // Drawing the white background + cv::rectangle(imageMat, origin + cv::Point(0, baseline), origin + cv::Point(textSize.width, -textSize.height) - cv::Point(0, padding), + cv::Scalar(255, 255, 255), cv::FILLED); + + // Putting black text on the white background + cv::putText(imageMat, text, origin, cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 0, 0), 1); + }; + + // Drawing text with background based on frame type + if(frame->getType() == OB_FRAME_COLOR && frame->getFormat() == OB_FORMAT_NV21) { + putTextWithBackground("Color-NV21", cv::Point(8, 16)); + } + else if(frame->getType() == OB_FRAME_COLOR && frame->getFormat() == OB_FORMAT_MJPG) { + putTextWithBackground("Color-MJPG", cv::Point(8, 16)); + } + else if(frame->getType() == OB_FRAME_COLOR && ((frame->getFormat() == OB_FORMAT_YUYV) || (frame->getFormat() == OB_FORMAT_YUY2))) { + putTextWithBackground("Color-YUYV", cv::Point(8, 16)); + } + else if(frame->getType() == OB_FRAME_DEPTH) { + putTextWithBackground("Depth", cv::Point(8, 16)); + } + else if(frame->getType() == OB_FRAME_IR) { + putTextWithBackground("IR", cv::Point(8, 16)); + } + else if(frame->getType() == OB_FRAME_IR_LEFT) { + putTextWithBackground("LeftIR", cv::Point(8, 16)); + } + else if(frame->getType() == OB_FRAME_IR_RIGHT) { + putTextWithBackground("RightIR", cv::Point(8, 16)); + } + + // Timestamp information with background + putTextWithBackground("frame timestamp(us): " + std::to_string(frame->getTimeStampUs()), cv::Point(8, 40)); + putTextWithBackground("system timestamp(us): " + std::to_string(frame->getSystemTimeStampUs()), cv::Point(8, 64)); +} + +cv::Mat CVWindow::resizeMatKeepAspectRatio(const cv::Mat &mat, int width, int height) { + auto hScale = static_cast(width) / mat.cols; + auto vScale = static_cast(height) / mat.rows; + auto scale = std::min(hScale, vScale); + auto newWidth = static_cast(mat.cols * scale); + auto newHeight = static_cast(mat.rows * scale); + cv::Mat resizeMat; + cv::resize(mat, resizeMat, cv::Size(newWidth, newHeight)); + + if(newWidth == width && newHeight == height) { + return resizeMat; + } + + // padding the resized mat to target width and height + cv::Mat paddedMat; + if(newWidth < width) { + auto paddingLeft = (width - newWidth) / 2; + auto paddingRight = width - newWidth - paddingLeft; + cv::copyMakeBorder(resizeMat, paddedMat, 0, 0, paddingLeft, paddingRight, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0)); + } + + if(newHeight < height) { + auto paddingTop = (height - newHeight) / 2; + auto paddingBottom = height - newHeight - paddingTop; + cv::copyMakeBorder(resizeMat, paddedMat, paddingTop, paddingBottom, 0, 0, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0)); + } + return paddedMat; +} + +} // namespace ob_smpl diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_opencv.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_opencv.hpp new file mode 100644 index 0000000..ea20787 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_opencv.hpp @@ -0,0 +1,117 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils_types.h" +#include "utils.hpp" + +namespace ob_smpl { + +// arrange type +typedef enum { + ARRANGE_SINGLE, // Only show the first frame + ARRANGE_ONE_ROW, // Arrange the frames in the array as a row + ARRANGE_ONE_COLUMN, // Arrange the frames in the array as a column + ARRANGE_GRID, // Arrange the frames in the array as a grid + ARRANGE_OVERLAY // Overlay the first two frames in the array +} ArrangeMode; + +class CVWindow { +public: + // create a window with the specified name, width and height + CVWindow(std::string name, uint32_t width = 1280, uint32_t height = 720, ArrangeMode arrangeMode = ARRANGE_SINGLE); + ~CVWindow() noexcept; + + // run the window loop + bool run(); + + // close window + void close(); + + // clear cached frames and mats + void reset(); + + // add frames to view + void pushFramesToView(std::vector> frames, int groupId = 0); + void pushFramesToView(std::shared_ptr currentFrame, int groupId = 0); + + // set show frame info + void setShowInfo(bool show); + + // set show frame syncTime info + void setShowSyncTimeInfo(bool show); + + // set alpha, only valid when arrangeMode_ is ARRANGE_OVERLAY + void setAlpha(float alpha); + + // set the window size + void resize(int width, int height); + + // set the key pressed callback + void setKeyPressedCallback(std::function callback); + + // set the key prompt + void setKeyPrompt(const std::string &prompt); + + // set the log message + void addLog(const std::string &log); + + // destroyWindow + void destroyWindow(); + +private: + // frames processing thread function + void processFrames(); + + // arrange frames in the renderMat_ according to the arrangeMode_ + void arrangeFrames(); + + // add info to mat + cv::Mat visualize(std::shared_ptr frame); + + // draw info to mat + void drawInfo(cv::Mat &imageMat, std::shared_ptr &frame); + + cv::Mat resizeMatKeepAspectRatio(const cv::Mat &mat, int width, int height); + +private: + std::string name_; + ArrangeMode arrangeMode_; + uint32_t width_; + uint32_t height_; + bool closed_; + bool showInfo_; + bool showSyncTimeInfo_; + bool isWindowDestroyed_; + float alpha_; + + std::thread processThread_; + std::map>> srcFrameGroups_; + std::mutex srcFrameGroupsMtx_; + std::condition_variable srcFrameGroupsCv_; + + using StreamsMatMap = std::map, cv::Mat>>; + StreamsMatMap matGroups_; + std::mutex renderMatsMtx_; + cv::Mat renderMat_; + + std::string prompt_; + bool showPrompt_; + uint64 winCreatedTime_; + + std::string log_; + uint64 logCreatedTime_; + + std::function keyPressedCallback_; +}; + +} // namespace ob_smpl diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_types.h b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_types.h new file mode 100644 index 0000000..cd1e09d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/examples/src/utils/utils_types.h @@ -0,0 +1,12 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESC_KEY 27 + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/ObSensor.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/ObSensor.h new file mode 100644 index 0000000..0eb17e7 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/ObSensor.h @@ -0,0 +1,23 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * \file ObSensor.h + * \brief This file serves as the C entrance for the OrbbecSDK library. + * It includes all necessary header files for OrbbecSDK usage. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/ObSensor.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/ObSensor.hpp new file mode 100644 index 0000000..23da2ff --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/ObSensor.hpp @@ -0,0 +1,21 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * \file ObSensor.hpp + * \brief This is the main entry point for the OrbbecSDK C++ library. + * It includes all necessary header files for using the library. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Advanced.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Advanced.h new file mode 100644 index 0000000..f6c81f0 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Advanced.h @@ -0,0 +1,314 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Get the current depth work mode. + * + * @param[in] device The device object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return ob_depth_work_mode The current depth work mode. + */ +OB_EXPORT ob_depth_work_mode ob_device_get_current_depth_work_mode(const ob_device *device, ob_error **error); + +/** + * @brief Get current depth mode name + * @brief According the current preset name to return current depth mode name + * @return const char* return the current depth mode name. + */ +OB_EXPORT const char *ob_device_get_current_depth_work_mode_name(const ob_device *device, ob_error **error); + +/** + * @brief Switch the depth work mode by ob_depth_work_mode. + * Prefer to use ob_device_switch_depth_work_mode_by_name to switch depth mode when the complete name of the depth work mode is known. + * + * @param[in] device The device object. + * @param[in] work_mode The depth work mode from ob_depth_work_mode_list which is returned by ob_device_get_depth_work_mode_list. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return ob_status The switch result. OB_STATUS_OK: success, other failed. + */ +OB_EXPORT ob_status ob_device_switch_depth_work_mode(ob_device *device, const ob_depth_work_mode *work_mode, ob_error **error); + +/** + * @brief Switch the depth work mode by work mode name. + * + * @param[in] device The device object. + * @param[in] mode_name The depth work mode name which is equal to ob_depth_work_mode.name. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return ob_status The switch result. OB_STATUS_OK: success, other failed. + */ +OB_EXPORT ob_status ob_device_switch_depth_work_mode_by_name(ob_device *device, const char *mode_name, ob_error **error); + +/** + * @brief Request the list of supported depth work modes. + * + * @param[in] device The device object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return ob_depth_work_mode_list The list of ob_depth_work_mode. + */ +OB_EXPORT ob_depth_work_mode_list *ob_device_get_depth_work_mode_list(const ob_device *device, ob_error **error); + +/** + * \if English + * @brief Get the depth work mode count that ob_depth_work_mode_list hold + * @param[in] work_mode_list data struct contain list of ob_depth_work_mode + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The total number contained in ob_depth_work_mode_list + * + */ +OB_EXPORT uint32_t ob_depth_work_mode_list_get_count(const ob_depth_work_mode_list *work_mode_list, ob_error **error); + +/** + * @brief Get the index target of ob_depth_work_mode from work_mode_list + * + * @param[in] work_mode_list Data structure containing a list of ob_depth_work_mode + * @param[in] index Index of the target ob_depth_work_mode + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_depth_work_mode + * + */ +OB_EXPORT ob_depth_work_mode ob_depth_work_mode_list_get_item(const ob_depth_work_mode_list *work_mode_list, uint32_t index, ob_error **error); + +/** + * @brief Free the resources of ob_depth_work_mode_list + * + * @param[in] work_mode_list Data structure containing a list of ob_depth_work_mode + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + */ +OB_EXPORT void ob_delete_depth_work_mode_list(ob_depth_work_mode_list *work_mode_list, ob_error **error); + +/** + * @brief Get the current preset name. + * @brief The preset mean a set of parameters or configurations that can be applied to the device to achieve a specific effect or function. + * + * @param device The device object. + * @param error Pointer to an error object that will be set if an error occurs. + * @return The current preset name, it should be one of the preset names returned by @ref ob_device_get_available_preset_list. + */ +OB_EXPORT const char *ob_device_get_current_preset_name(const ob_device *device, ob_error **error); + +/** + * @brief Get the available preset list. + * @attention After loading the preset, the settings in the preset will set to the device immediately. Therefore, it is recommended to re-read the device + * settings to update the user program temporarily. + * + * @param device The device object. + * @param preset_name Pointer to an error object that will be set if an error occurs. The name should be one of the preset names returned by @ref + * ob_device_get_available_preset_list. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_load_preset(ob_device *device, const char *preset_name, ob_error **error); + +/** + * @brief Load preset from json string. + * @brief After loading the custom preset, the settings in the custom preset will set to the device immediately. + * @brief After loading the custom preset, the available preset list will be appended with the custom preset and named as the file name. + * + * @param device The device object. + * @param json_file_path The json file path. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_load_preset_from_json_file(ob_device *device, const char *json_file_path, ob_error **error); + +/** + * @brief Load custom preset from data. + * @brief After loading the custom preset, the settings in the custom preset will set to the device immediately. + * @brief After loading the custom preset, the available preset list will be appended with the custom preset and named as the @ref presetName. + * + * @attention The user should ensure that the custom preset data is adapted to the device and the settings in the data are valid. + * @attention It is recommended to re-read the device settings to update the user program temporarily after successfully loading the custom preset. + * + * @param data The custom preset data. + * @param size The size of the custom preset data. + */ +OB_EXPORT void ob_device_load_preset_from_json_data(ob_device *device, const char *presetName, const uint8_t *data, uint32_t size, ob_error **error); + +/** + * @brief Export current settings as a preset json file. + * @brief After exporting the custom preset, the available preset list will be appended with the custom preset and named as the file name. + * + * @param device The device object. + * @param json_file_path The json file path. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_export_current_settings_as_preset_json_file(ob_device *device, const char *json_file_path, ob_error **error); + +/** + * @brief Export current device settings as a preset json data. + * @brief After exporting the preset, a new preset named as the @ref presetName will be added to the available preset list. + * + * @attention The memory of the data is allocated by the SDK, and will automatically be released by the SDK. + * @attention The memory of the data will be reused by the SDK on the next call, so the user should copy the data to a new buffer if it needs to be + * preserved. + * + * @param[out] data return the preset json data. + * @param[out] dataSize return the size of the preset json data. + */ +OB_EXPORT void ob_device_export_current_settings_as_preset_json_data(ob_device *device, const char *presetName, const uint8_t **data, uint32_t *dataSize, + ob_error **error); + +/** + * @brief Get the available preset list. + * + * @param device The device object. + * @param error Pointer to an error object that will be set if an error occurs. + * @return The available preset list. + */ +OB_EXPORT ob_device_preset_list *ob_device_get_available_preset_list(const ob_device *device, ob_error **error); + +/** + * @brief Delete the available preset list. + * + * @param preset_list The available preset list. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_preset_list(ob_device_preset_list *preset_list, ob_error **error); + +/** + * @brief Get the number of preset in the preset list. + * + * @param preset_list The available preset list. + * @param error Pointer to an error object that will be set if an error occurs. + * @return The number of preset in the preset list. + */ +OB_EXPORT uint32_t ob_device_preset_list_get_count(const ob_device_preset_list *preset_list, ob_error **error); + +/** + * @brief Get the name of the preset in the preset list. + * + * @param preset_list The available preset list. + * @param index The index of the preset in the preset list. + * @param error Pointer to an error object that will be set if an error occurs. + * @return The name of the preset in the preset list. + */ +OB_EXPORT const char *ob_device_preset_list_get_name(const ob_device_preset_list *preset_list, uint32_t index, ob_error **error); + +/** + * @brief Check if the preset list has the preset. + * + * @param preset_list The available preset list. + * @param preset_name The name of the preset. + * @param error Pointer to an error object that will be set if an error occurs. + * @return Whether the preset list has the preset. If true, the preset list has the preset. If false, the preset list does not have the preset. + */ +OB_EXPORT bool ob_device_preset_list_has_preset(const ob_device_preset_list *preset_list, const char *preset_name, ob_error **error); + +/** + * @brief Check if the device supports the frame interleave feature. + * + * @param device The device object. + * @param error Pointer to an error object that will be set if an error occurs. + * @return bool Returns true if the device supports the frame interleave feature. + */ +OB_EXPORT bool ob_device_is_frame_interleave_supported(const ob_device *device, ob_error **error); +/** + * + * @brief load the frame interleave mode according to frame interleavee name. + * + * @param device The device object. + * @param frame_interleave_name The name should be one of the frame interleave names returned by @ref ob_device_get_available_frame_interleave_list. + * @param error Log error messages. + */ +OB_EXPORT void ob_device_load_frame_interleave(ob_device *device, const char *frame_interleave_name, ob_error **error); + +/** + * @brief Get the available frame interleave list. + * + * @param device The device object. + * @param error Log error messages. + * @return The available frame interleave list. + */ +OB_EXPORT ob_device_frame_interleave_list *ob_device_get_available_frame_interleave_list(ob_device *device, ob_error **error); + +/** + * @brief Delete the available frame interleave list. + * + * @param frame_interleave_list The available frame interleave list. + * @param error Log error messages. + */ +OB_EXPORT void ob_delete_frame_interleave_list(ob_device_frame_interleave_list *frame_interleave_list, ob_error **error); + +/** + * @brief Get the number of frame interleave in the frame interleave list. + * + * @param frame_interleave_list The available frame interleave list. + * @param error Log error messages. + * @return The number of frame interleave in the frame interleave list. + */ +OB_EXPORT uint32_t ob_device_frame_interleave_list_get_count(ob_device_frame_interleave_list *frame_interleave_list, ob_error **error); + +/** + * @brief Get the name of frame interleave in the frame interleave list. + * + * @param frame_interleave_list The available frame interleave list. + * @param index The index of frame interleave in the frame interleave list. + * @param error Log error messages. + * @return The name of frame interleave in the frame interleave list.. + */ +OB_EXPORT const char *ob_device_frame_interleave_list_get_name(ob_device_frame_interleave_list *frame_interleave_list, uint32_t index, ob_error **error); + +/** + * @brief Check if the interleave ae list has the interleave ae. + * + * @param frame_interleave_list The available interleave ae list. + * @param frame_interleave_name The name of the interleave ae. + * @param error Log error messages. + * @return Whether the interleave ae list has the interleave ae. If true, the interleave ae list has the interleave ae. If false, the interleave ae list does + * not have the interleave ae. + */ +OB_EXPORT bool ob_device_frame_interleave_list_has_frame_interleave(ob_device_frame_interleave_list *frame_interleave_list, const char *frame_interleave_name, + ob_error **error); + +/* @brief Get the available preset resolution config list. + * + * @param device The device object. + * @param error Log error messages. + * @return The available frame resolution config list. + * + */ +OB_EXPORT ob_preset_resolution_config_list *ob_device_get_available_preset_resolution_config_list(ob_device *device, ob_error **error); + +/* @brief Get the number of preset resolution in the preset resolution list. + * + * @param ob_preset_resolution_config_list The available preset resolution list. + * @param error Log error messages. + * @return The number of preset resolution in the preset resolution list. + */ +OB_EXPORT uint32_t ob_device_preset_resolution_config_get_count(ob_preset_resolution_config_list *ob_preset_resolution_config_list, ob_error **error); + +/** + * @brief Get the preset resolution in the preset resolution list. + * + * @param ob_preset_resolution_config_list The available preset resolution list. + * @param index The index of preset resolution in the preset resolution list. + * @param error Log error messages. + * @return The preset resolution in the preset resolution list. + */ +OB_EXPORT OBPresetResolutionConfig ob_device_preset_resolution_config_list_get_item(const ob_preset_resolution_config_list *ob_preset_resolution_config_list, + uint32_t index, ob_error **error); + +/** + * @brief Delete the available preset resolution list. + * + * @param frame_interleave_list The available preset resolution list. + * @param error Log error messages. + */ +OB_EXPORT void ob_delete_preset_resolution_config_list(ob_preset_resolution_config_list *ob_preset_resolution_config_list, ob_error **error); + +// The following interfaces are deprecated and are retained here for compatibility purposes. +#define ob_depth_work_mode_list_count ob_depth_work_mode_list_get_count +#define ob_device_preset_list_count ob_device_preset_list_get_count + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Context.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Context.h new file mode 100644 index 0000000..e423b0a --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Context.h @@ -0,0 +1,187 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Context.h + * @brief Context is a management class that describes the runtime of the SDK and is responsible for resource allocation and release of the SDK. + * Context has the ability to manage multiple devices. It is responsible for enumerating devices, monitoring device callbacks, and enabling multi-device + * synchronization. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Create a context object with the default configuration file + * + * @param[out] error Pointer to an error object that will be populated if an error occurs during context creation + * @return Pointer to the created context object + */ +OB_EXPORT ob_context *ob_create_context(ob_error **error); + +/** + * @brief Create a context object with a specified configuration file + * + * @param[in] config_file_path Path to the configuration file. If NULL, the default configuration file will be used. + * @param[out] error Pointer to an error object that will be populated if an error occurs during context creation + * @return Pointer to the created context object + */ +OB_EXPORT ob_context *ob_create_context_with_config(const char *config_file_path, ob_error **error); + +/** + * @brief Delete a context object + * + * @param[in] context Pointer to the context object to be deleted + * @param[out] error Pointer to an error object that will be populated if an error occurs during context deletion + */ +OB_EXPORT void ob_delete_context(ob_context *context, ob_error **error); + +/** + * @brief Get a list of enumerated devices + * + * @param[in] context Pointer to the context object + * @param[out] error Pointer to an error object that will be populated if an error occurs during device enumeration + * @return Pointer to the device list object + */ +OB_EXPORT ob_device_list *ob_query_device_list(ob_context *context, ob_error **error); + +/** + * @brief Enable or disable network device enumeration + * @brief After enabling, the network device will be automatically discovered and can be retrieved through @ref ob_query_device_list. The default state can be + * set in the configuration file. + * + * @attention Network device enumeration is performed through the GVCP protocol. If the device is not in the same subnet as the host, it will be discovered but + * cannot be connected. + * + * @param[in] context Pointer to the context object + * @param[in] enable true to enable, false to disable + * @param[out] error Pointer to an error object that will be populated if an error occurs. + */ +OB_EXPORT void ob_enable_net_device_enumeration(ob_context *context, bool enable, ob_error **error); + +/** + * @brief "Force" a static IP address configuration in a device identified by its MAC Address. + * + * @param[in] macAddress MAC address of the network device. + * You can obtain it from @ref DeviceList::uid(), or specify it manually + * in the format xx:xx:xx:xx:xx:xx, where each xx is a two-digit hexadecimal value. + * @param[in] config The new IP configuration. + * @param[out] error Pointer to an error object that will be populated if an error occurs. + * @return bool true if the configuration command was processed successfully, false otherwise. + * + * @note This applies to all Orbbec GigE Vision devices + */ +OB_EXPORT bool ob_force_ip_config(const char *deviceUid, ob_net_ip_config config, ob_error **error); + +/** + * @brief Create a network device object + * + * @param[in] context Pointer to the context object + * @param[in] address IP address of the device + * @param[in] port Port number of the device + * @param[out] error Pointer to an error object that will be populated if an error occurs during device creation + * @return Pointer to the created device object + */ +OB_EXPORT ob_device *ob_create_net_device(ob_context *context, const char *address, uint16_t port, ob_error **error); + +/** + * @brief Set a device plug-in callback function + * @attention The added and removed device lists returned through the callback interface need to be released manually + * @attention This function supports multiple callbacks. Each call to this function adds a new callback to an internal list. + * + * @param[in] context Pointer to the context object + * @param[in] callback Pointer to the callback function triggered when a device is plugged or unplugged + * @param[in] user_data Pointer to user data that can be passed to and retrieved from the callback function + * @param[out] error Pointer to an error object that will be populated if an error occurs during callback function setting + */ +OB_EXPORT void ob_set_device_changed_callback(ob_context *context, ob_device_changed_callback callback, void *user_data, ob_error **error); + +/** + * @brief Activates device clock synchronization to synchronize the clock of the host and all created devices (if supported). + * + * @param[in] context Pointer to the context object + * @param[in] repeat_interval_msec The interval for auto-repeated synchronization, in milliseconds. If the value is 0, synchronization is performed only once. + * @param[out] error Pointer to an error object that will be populated if an error occurs during execution + */ +OB_EXPORT void ob_enable_device_clock_sync(ob_context *context, uint64_t repeat_interval_msec, ob_error **error); + +/** + * @brief Free idle memory from the internal frame memory pool + * + * @param[in] context Pointer to the context object + * @param[out] error Pointer to an error object that will be populated if an error occurs during memory freeing + */ +OB_EXPORT void ob_free_idle_memory(ob_context *context, ob_error **error); + +/** + * @brief For linux, there are two ways to enable the UVC backend: libuvc and v4l2. This function is used to set the backend type. + * @brief It is effective when the new device is created. + * + * @attention This interface is only available for Linux. + * + * @param[in] context Pointer to the context object + * @param[in] backend_type The backend type to be used. + * @param[out] error Pointer to an error object that will be populated if an error occurs during backend type setting + */ +OB_EXPORT void ob_set_uvc_backend_type(ob_context *context, ob_uvc_backend_type backend_type, ob_error **error); + +/** + * @brief Set the global log level + * + * @attention This interface setting will affect the output level of all logs (terminal, file, callback) + * + * @param[in] severity Log level to set + * @param[out] error Pointer to an error object that will be populated if an error occurs during log level setting + */ +OB_EXPORT void ob_set_logger_severity(ob_log_severity severity, ob_error **error); + +/** + * @brief Set the log output to a file + * + * @param[in] severity Log level to output to file + * @param[in] directory Path to the log file output directory. If the path is empty, the existing settings will continue to be used (if the existing + * configuration is also empty, the log will not be output to the file) + * @param[out] error Pointer to an error object that will be populated if an error occurs during log output setting + */ +OB_EXPORT void ob_set_logger_to_file(ob_log_severity severity, const char *directory, ob_error **error); + +/** + * @brief Set the log callback function + * + * @param[in] severity Log level to set for the callback function + * @param[in] callback Pointer to the callback function + * @param[in] user_data Pointer to user data that can be passed to and retrieved from the callback function + * @param[out] error Pointer to an error object that will be populated if an error occurs during log callback function setting + */ +OB_EXPORT void ob_set_logger_to_callback(ob_log_severity severity, ob_log_callback callback, void *user_data, ob_error **error); + +/** + * @brief Set the log output to the console + * + * @param[in] severity Log level to output to the console + * @param[out] error Pointer to an error object that will be populated if an error occurs during log output setting + */ +OB_EXPORT void ob_set_logger_to_console(ob_log_severity severity, ob_error **error); + +/** + * @brief Set the extensions directory + * @brief The extensions directory is used to search for dynamic libraries that provide additional functionality to the SDK, such as the Frame filters. + * + * @attention Should be called before creating the context and pipeline, otherwise the default extensions directory (./extensions) will be used. + * + * @param directory Path to the extensions directory. If the path is empty, extensions path will be set to the current working directory. + * @param error Pointer to an error object that will be populated if an error occurs during extensions directory setting + */ +OB_EXPORT void ob_set_extensions_directory(const char *directory, ob_error **error); + +// The following interfaces are deprecated and are retained here for compatibility purposes. +#define ob_enable_multi_device_sync ob_enable_device_clock_sync +#define ob_set_logger_callback ob_set_logger_to_callback + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Device.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Device.h new file mode 100644 index 0000000..3b1074c --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Device.h @@ -0,0 +1,768 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Device.h + * @brief Device-related functions, including operations such as obtaining and creating a device, setting and obtaining device property, and obtaining sensors + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" +#include "Property.h" +#include "MultipleDevices.h" +#include "Advanced.h" + +/** + * @brief Delete a device. + * + * @param[in] device The device to be deleted. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_device(ob_device *device, ob_error **error); + +/** + * @brief List all sensors. + * + * @param[in] device The device object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_sensor_list* The list of all sensors. + */ +OB_EXPORT ob_sensor_list *ob_device_get_sensor_list(const ob_device *device, ob_error **error); + +/** + * @brief Get a device's sensor. + * + * @param[in] device The device object. + * @param[in] type The type of sensor to get. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_sensor* The acquired sensor. + */ +OB_EXPORT ob_sensor *ob_device_get_sensor(ob_device *device, ob_sensor_type type, ob_error **error); + +/** + * @brief Set an integer type of device property. + * + * @param[in] device The device object. + * @param[in] property_id The ID of the property to be set. + * @param[in] value The property value to be set. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_set_int_property(ob_device *device, ob_property_id property_id, int32_t value, ob_error **error); + +/** + * @brief Get an integer type of device property. + * + * @param[in] device The device object. + * @param[in] property_id The property ID. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return int32_t The property value. + */ +OB_EXPORT int32_t ob_device_get_int_property(ob_device *device, ob_property_id property_id, ob_error **error); + +/** + * @brief Get the integer type of device property range. + * + * @param[in] device The device object. + * @param[in] property_id The property id. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The property range. + */ +OB_EXPORT ob_int_property_range ob_device_get_int_property_range(ob_device *device, ob_property_id property_id, ob_error **error); + +/** + * @brief Set a float type of device property. + * + * @param[in] device The device object. + * @param[in] property_id The ID of the property to be set. + * @param[in] value The property value to be set. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_set_float_property(ob_device *device, ob_property_id property_id, float value, ob_error **error); + +/** + * @brief Get a float type of device property. + * + * @param[in] device The device object. + * @param[in] property_id The property ID. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return float The property value. + */ +OB_EXPORT float ob_device_get_float_property(ob_device *device, ob_property_id property_id, ob_error **error); + +/** + * @brief Get the float type of device property range. + * + * @param[in] device The device object. + * @param[in] property_id The property id. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The property range. + */ +OB_EXPORT ob_float_property_range ob_device_get_float_property_range(ob_device *device, ob_property_id property_id, ob_error **error); + +/** + * @brief Set a boolean type of device property. + * + * @param[in] device The device object. + * @param[in] property_id The ID of the property to be set. + * @param[in] value The property value to be set. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_set_bool_property(ob_device *device, ob_property_id property_id, bool value, ob_error **error); + +/** + * @brief Get a boolean type of device property. + * + * @param[in] device The device object. + * @param[in] property_id The property ID. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return bool The property value. + */ +OB_EXPORT bool ob_device_get_bool_property(ob_device *device, ob_property_id property_id, ob_error **error); + +/** + * @brief Get the boolean type of device property range. + * + * @param[in] device The device object. + * @param[in] property_id The property id. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The property range. + */ +OB_EXPORT ob_bool_property_range ob_device_get_bool_property_range(ob_device *device, ob_property_id property_id, ob_error **error); + +/** + * @brief Set structured data. + * + * @param[in] device The device object. + * @param[in] property_id The ID of the property to be set. + * @param[in] data The property data to be set. + * @param[in] data_size The size of the property to be set. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_set_structured_data(ob_device *device, ob_property_id property_id, const uint8_t *data, uint32_t data_size, ob_error **error); + +/** + * @brief Get structured data of a device property. + * + * @param[in] device The device object. + * @param[in] property_id The ID of the property. + * @param[out] data The obtained property data. + * @param[out] data_size The size of the obtained property data. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_get_structured_data(ob_device *device, ob_property_id property_id, uint8_t *data, uint32_t *data_size, ob_error **error); + +/** + * @brief Get raw data of a device property. + * + * @param[in] device The device object. + * @param[in] property_id The ID of the property. + * @param[out] cb The get data callback. + * @param[out] user_data User-defined data that will be returned in the callback. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_get_raw_data(ob_device *device, ob_property_id property_id, ob_get_data_callback cb, void *user_data, ob_error **error); + +/** + * @brief Set customer data. + * + * @param[in] device The device object. + * @param[in] data The property data to be set. + * @param[in] data_size The size of the property to be set,the maximum length cannot exceed 65532 bytes. + * @param[out] error Log error messages. + */ +OB_EXPORT void ob_device_write_customer_data(ob_device *device, const void *data, uint32_t data_size, ob_error **error); + +/** + * @brief Get customer data of a device property. + * + * @param[in] device The device object. + * @param[out] data The obtained property data. + * @param[out] data_size The size of the obtained property data. + * @param[out] error Log error messages. + */ +OB_EXPORT void ob_device_read_customer_data(ob_device *device, void *data, uint32_t *data_size, ob_error **error); + +/** + * @brief Get the number of properties supported by the device. + * + * @param[in] device The device object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The number of properties supported by the device. + */ +OB_EXPORT uint32_t ob_device_get_supported_property_count(const ob_device *device, ob_error **error); + +/** + * @brief Get the type of property supported by the device. + * + * @param[in] device The device object. + * @param[in] index The property index. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The type of property supported by the device. + */ +OB_EXPORT ob_property_item ob_device_get_supported_property_item(const ob_device *device, uint32_t index, ob_error **error); + +/** + * @brief Check if a device property permission is supported. + * + * @param[in] device The device object. + * @param[in] property_id The property id. + * @param[in] permission The type of permission that needs to be interpreted. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return Whether the property permission is supported. + */ +OB_EXPORT bool ob_device_is_property_supported(const ob_device *device, ob_property_id property_id, ob_permission_type permission, ob_error **error); + +/** + * @brief Check if the device supports global timestamp. + * + * @param[in] device The device object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return bool Whether the device supports global timestamp. + */ +OB_EXPORT bool ob_device_is_global_timestamp_supported(const ob_device *device, ob_error **error); + +/** + * @brief Enable or disable global timestamp. + * + * @param device The device object. + * @param enable Whether to enable global timestamp. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_enable_global_timestamp(ob_device *device, bool enable, ob_error **error); + +/** + * @brief Update the device firmware. + * + * @param[in] device The device object. + * @param[in] path The firmware path. + * @param[in] callback The firmware upgrade progress callback. + * @param[in] async Whether to execute asynchronously. + * @param[in] user_data User-defined data that will be returned in the callback. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_update_firmware(ob_device *device, const char *path, ob_device_fw_update_callback callback, bool async, void *user_data, + ob_error **error); + +/** + * @brief Update the device firmware from data. + * + * @param[in] device The device object. + * @param[in] data The firmware file data. + * @param[in] data_size The firmware file size. + * @param[in] callback The firmware upgrade progress callback. + * @param[in] async Whether to execute asynchronously. + * @param[in] user_data User-defined data that will be returned in the callback. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_update_firmware_from_data(ob_device *device, const uint8_t *data, uint32_t data_size, ob_device_fw_update_callback callback, + bool async, void *user_data, ob_error **error); + +/** + * @brief Update the device optional depth presets. + * + * @param[in] device The device object. + * @param[in] file_path_list A list(2D array) of preset file paths, each up to OB_PATH_MAX characters. + * @param[in] path_count The number of the preset file paths. + * @param[in] callback The preset upgrade progress callback. + * @param[in] user_data User-defined data that will be returned in the callback. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_update_optional_depth_presets(ob_device *device, const char file_path_list[][OB_PATH_MAX], uint8_t path_count, + ob_device_fw_update_callback callback, void *user_data, ob_error **error); + +/** + * @brief Device reboot + * @attention The device will be disconnected and reconnected. After the device is disconnected, the interface access to the device handle may be abnormal. + * Please use the ob_delete_device interface to delete the handle directly. After the device is reconnected, it can be obtained again. + * + * @param[in] device Device object + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_reboot(ob_device *device, ob_error **error); + +/** + * @brief Get the current device status. + * + * @param[in] device The device object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return ob_device_state The device state information. + */ +OB_EXPORT ob_device_state ob_device_get_device_state(const ob_device *device, ob_error **error); + +/** + * @brief Set the device state changed callback. + * + * @param[in] device The device object. + * @param[in] callback The callback function to be called when the device status changes. + * @param[in] user_data User-defined data that will be returned in the callback. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_set_state_changed_callback(ob_device *device, ob_device_state_callback callback, void *user_data, ob_error **error); + +/** + * @brief Enable or disable the device heartbeat. + * @brief After enable the device heartbeat, the sdk will start a thread to send heartbeat signal to the device error every 3 seconds. + + * @attention If the device does not receive the heartbeat signal for a long time, it will be disconnected and rebooted. + * + * @param[in] device The device object. + * @param[in] enable Whether to enable the device heartbeat. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_enable_heartbeat(ob_device *device, bool enable, ob_error **error); + +/** + * @brief Send data to the device and receive data from the device. + * @brief This is a factory and debug function, which can be used to send and receive data from the device. The data format is secret and belongs to the device + * vendor. + * + * @param[in] device The device object. + * @param[in] send_data The data to be sent to the device. + * @param[in] send_data_size The size of the data to be sent to the device. + * @param[out] receive_data The data received from the device. + * @param[in,out] receive_data_size Pass in the expected size of the receive data, and return the actual size of the received data. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_send_and_receive_data(ob_device *device, const uint8_t *send_data, uint32_t send_data_size, uint8_t *receive_data, + uint32_t *receive_data_size, ob_error **error); + +/** + * @brief Get device information. + * + * @param[in] device The device to obtain information from. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_device_info* The device information. + */ +OB_EXPORT ob_device_info *ob_device_get_device_info(const ob_device *device, ob_error **error); + +/** + * @brief Delete device information. + * + * @param[in] info The device information to be deleted. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_device_info(ob_device_info *info, ob_error **error); + +/** + * @brief Get device name + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* return the device name + */ +OB_EXPORT const char *ob_device_info_get_name(const ob_device_info *info, ob_error **error); + +/** +* @brief Get device pid + + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return int return the device pid +*/ +OB_EXPORT int ob_device_info_get_pid(const ob_device_info *info, ob_error **error); + +/** + * @brief Get device vid + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return int return device vid + */ +OB_EXPORT int ob_device_info_get_vid(const ob_device_info *info, ob_error **error); + +/** + * @brief Get device uid + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* return device uid + */ +OB_EXPORT const char *ob_device_info_get_uid(const ob_device_info *info, ob_error **error); + +/** + * @brief Get device serial number + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* return device serial number + */ +OB_EXPORT const char *ob_device_info_get_serial_number(const ob_device_info *info, ob_error **error); + +/** + * @brief Get the firmware version number + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return int return the firmware version number + */ +OB_EXPORT const char *ob_device_info_get_firmware_version(const ob_device_info *info, ob_error **error); + +/** + * @brief Get the device connection type + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The connection type, currently supports: "USB", "USB1.0", "USB1.1", "USB2.0", "USB2.1", "USB3.0", "USB3.1", "USB3.2", "Ethernet" + */ +OB_EXPORT const char *ob_device_info_get_connection_type(const ob_device_info *info, ob_error **error); + +/** + * @brief Get the device IP address + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0" + * + * @param info Device Information + * @param error Pointer to an error object that will be set if an error occurs. + * @return const char* The IP address, such as "192.168.1.10" + */ +OB_EXPORT const char *ob_device_info_get_ip_address(const ob_device_info *info, ob_error **error); + +/** + * @brief Get the network device subnet mask + * + * @param[in] info Device information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The subnet mask, such as "255.255.255.0" + */ +OB_EXPORT const char *ob_device_info_get_subnet_mask(const ob_device_info *info, ob_error **error); + +/** + * @brief Get the network device gateway address + * + * @param[in] info Device information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The gateway address, such as "192.168.1.1" + */ +OB_EXPORT const char *ob_device_info_get_gateway(const ob_device_info *info, ob_error **error); + +/** + * @brief Get the hardware version number + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The hardware version number + */ +OB_EXPORT const char *ob_device_info_get_hardware_version(const ob_device_info *info, ob_error **error); + +/** + * @brief Check if the device extension information exists. + * + * @param device The device object. + * @param info_key The key of the device extension information. + * @param error Pointer to an error object that will be set if an error occurs. + * @return bool Whether the device extension information exists. + */ +OB_EXPORT bool ob_device_is_extension_info_exist(const ob_device *device, const char *info_key, ob_error **error); + +/** + * @brief Get the device extension information. + * @brief Extension information is a set of key-value pair of string, user cat get the information by the key. + * + * @param[in] device The device object. + * @param[in] info_key The key of the device extension information. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The device extension information + */ +OB_EXPORT const char *ob_device_get_extension_info(const ob_device *device, const char *info_key, ob_error **error); + +/** + * @brief Get the minimum SDK version number supported by the device + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The minimum SDK version number supported by the device + */ +OB_EXPORT const char *ob_device_info_get_supported_min_sdk_version(const ob_device_info *info, ob_error **error); + +/** + * @brief Get the chip name + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The ASIC name + */ +OB_EXPORT const char *ob_device_info_get_asicName(const ob_device_info *info, ob_error **error); + +/** + * @brief Get the device type + * + * @param[in] info Device Information + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_device_type The device type + */ +OB_EXPORT ob_device_type ob_device_info_get_device_type(const ob_device_info *info, ob_error **error); + +/** + * @brief Delete a device list. + * + * @param[in] list The device list object to be deleted. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_device_list(ob_device_list *list, ob_error **error); + +/** + * @brief Get the number of devices + * + * @param[in] list Device list object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the number of devices + */ +OB_EXPORT uint32_t ob_device_list_get_count(const ob_device_list *list, ob_error **error); + +/** + * @brief Get device name + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* return device name + */ +OB_EXPORT const char *ob_device_list_get_device_name(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get the pid of the specified device + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return int return the device pid + */ +OB_EXPORT int ob_device_list_get_device_pid(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get the vid of the specified device + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return int return device vid + */ +OB_EXPORT int ob_device_list_get_device_vid(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get the uid of the specified device + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* return the device uid + */ +OB_EXPORT const char *ob_device_list_get_device_uid(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get the serial number of the specified device. + * + * @param[in] list Device list object. + * @param[in] index Device index. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The device UID. + */ +OB_EXPORT const char *ob_device_list_get_device_serial_number(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get device connection type + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* returns the device connection type, currently supports: "USB", "USB1.0", "USB1.1", "USB2.0", "USB2.1", "USB3.0", "USB3.1", "USB3.2", + * "Ethernet" + */ +OB_EXPORT const char *ob_device_list_get_device_connection_type(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get device ip address + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0". + * + * @param list Device list object + * @param index Device index + * @param error Pointer to an error object that will be set if an error occurs. + * @return const char* returns the device ip address, such as "192.168.1.10" + */ +OB_EXPORT const char *ob_device_list_get_device_ip_address(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get device subnet mask + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0". + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* returns the device subnet mask, such as "255.255.255.0" + */ +OB_EXPORT const char *ob_device_list_get_device_subnet_mask(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get device gateway + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0". + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* returns the device gateway, such as "192.168.1.1" + */ +OB_EXPORT const char *ob_device_list_get_device_gateway(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get the MAC address of the host network interface corresponding to the network device. + * + * @attention Only valid for network devices. Returns "0:0:0:0:0:0" for non-network devices. + * + * @param list Device list object + * @param index Device index + * @param error Pointer to an error object that will be set if an error occurs. + * @return const char* The MAC address of the host network interface associated with the device. + */ +OB_EXPORT const char *ob_device_list_get_device_local_mac(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get the IP address of the host network interface corresponding to the network device. + * + * @attention Only valid for network devices. Returns "0.0.0.0" for non-network devices. + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The IP address of the host network interface associated with the device. + */ +OB_EXPORT const char *ob_device_list_get_device_local_ip(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get the subnet length of the host network interface corresponding to the network device. + * + * @attention Only valid for network devices. Returns 0 for non-network devices. + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const uint8_t The subnet length (0~32) of the host network interface associated with the device. + */ +OB_EXPORT uint8_t ob_device_list_get_device_local_subnet_length(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Get the gateway of the host network interface corresponding to the network device. + * + * @attention Only valid for network devices. Returns "0.0.0.0" for non-network devices. + * + * @param[in] list Device list object + * @param[in] index Device index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const char* The gateway of the host network interface associated with the device. + */ +OB_EXPORT const char *ob_device_list_get_device_local_gateway(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Create a device. + * + * @attention If the device has already been acquired and created elsewhere, repeated acquisitions will return an error. + * + * @param[in] list Device list object. + * @param[in] index The index of the device to create. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_device* The created device. + * + */ +OB_EXPORT ob_device *ob_device_list_get_device(const ob_device_list *list, uint32_t index, ob_error **error); + +/** + * @brief Create a device. + * + * @attention If the device has already been acquired and created elsewhere, repeated acquisitions will return an error. + * + * @param[in] list Device list object. + * @param[in] serial_number The serial number of the device to create. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_device* The created device. + */ +OB_EXPORT ob_device *ob_device_list_get_device_by_serial_number(const ob_device_list *list, const char *serial_number, ob_error **error); + +/** + * @brief Create device by uid + * @brief On Linux platform, for usb device, the uid of the device is composed of bus-port-dev, for example 1-1.2-1. But the SDK will remove the dev number and + * only keep the bus-port as the uid to create the device, for example 1-1.2, so that we can create a device connected to the specified USB port. Similarly, + * users can also directly pass in bus-port as uid to create device. + * @brief For GMSL device, the uid is GMSL port with "gmsl2-" prefix, for example gmsl2-1. + * + * @attention If the device has already been acquired and created elsewhere, repeated acquisitions will return an error. + * + * @param[in] list Device list object. + * @param[in] uid The UID of the device to create. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_device* The created device. + */ +OB_EXPORT ob_device *ob_device_list_get_device_by_uid(const ob_device_list *list, const char *uid, ob_error **error); + +/** + * @brief Get the original parameter list of camera calibration saved on the device. + * + * @attention The parameters in the list do not correspond to the current open-stream configuration.You need to select the parameters according to the actual + * situation, and may need to do scaling, mirroring and other processing. Non-professional users are recommended to use the ob_pipeline_get_camera_param() + * interface. + * + * @param[in] device The device object. + * @param[out] error Log error messages. + * + * @return ob_camera_param_list The camera parameter list. + */ +OB_EXPORT ob_camera_param_list *ob_device_get_calibration_camera_param_list(ob_device *device, ob_error **error); + +/** + * @brief Get the number of camera parameter lists + * + * @param[in] param_list Camera parameter list + * @param[out] error Log error messages + * @return uint32_t The number of lists + */ +OB_EXPORT uint32_t ob_camera_param_list_get_count(ob_camera_param_list *param_list, ob_error **error); + +/** + * @brief Get camera parameters from the camera parameter list + * + * @param[in] param_list Camera parameter list + * @param[in] index Parameter index + * @param[out] error Log error messages + * @return ob_camera_param The camera parameters. Since it returns the structure object directly, there is no need to provide a delete interface. + */ +OB_EXPORT ob_camera_param ob_camera_param_list_get_param(ob_camera_param_list *param_list, uint32_t index, ob_error **error); + +/** + * @brief Delete the camera parameter list + * + * @param[in] param_list Camera parameter list + * @param[out] error Log error messages + */ +OB_EXPORT void ob_delete_camera_param_list(ob_camera_param_list *param_list, ob_error **error); + +// The following interfaces are deprecated and are retained here for compatibility purposes. +#define ob_device_list_device_count ob_device_list_get_count +#define ob_device_list_get_extension_info ob_device_info_get_extension_info +#define ob_device_upgrade ob_device_update_firmware +#define ob_device_upgrade_from_data ob_device_update_firmware_from_data +#define ob_device_get_supported_property ob_device_get_supported_property_item +#define ob_device_state_changed ob_device_set_state_changed_callback +#define ob_device_info_name ob_device_info_get_name +#define ob_device_info_pid ob_device_info_get_pid +#define ob_device_info_vid ob_device_info_get_vid +#define ob_device_info_uid ob_device_info_get_uid +#define ob_device_info_serial_number ob_device_info_get_serial_number +#define ob_device_info_firmware_version ob_device_info_get_firmware_version +#define ob_device_info_connection_type ob_device_info_get_connection_type +#define ob_device_info_ip_address ob_device_info_get_ip_address +#define ob_device_info_hardware_version ob_device_info_get_hardware_version +#define ob_device_info_supported_min_sdk_version ob_device_info_get_supported_min_sdk_version +#define ob_device_info_asicName ob_device_info_get_asicName +#define ob_device_info_device_type ob_device_info_get_device_type +#define ob_device_list_get_device_count ob_device_list_get_count +#define ob_camera_param_list_count ob_camera_param_list_get_count + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Error.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Error.h new file mode 100644 index 0000000..b9aec31 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Error.h @@ -0,0 +1,84 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Error.h + * @brief Functions for handling errors, mainly used for obtaining error messages. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Create a new error object. + * + * @param status The error status. + * @param message The error message. + * @param function The name of the API function that caused the error. + * @param args The error parameters. + * @param exception_type The type of exception that caused the error. + * @return ob_error* The new error object. + */ +OB_EXPORT ob_error *ob_create_error(ob_status status, const char *message, const char *function, const char *args, ob_exception_type exception_type); + +/** + * @brief Get the error status. + * + * @param[in] error The error object. + * @return The error status. + */ +OB_EXPORT ob_status ob_error_get_status(const ob_error *error); + +/** + * @brief Get the error message. + * + * @param[in] error The error object. + * @return The error message. + */ +OB_EXPORT const char *ob_error_get_message(const ob_error *error); + +/** + * @brief Get the name of the API function that caused the error. + * + * @param[in] error The error object. + * @return The name of the API function. + */ +OB_EXPORT const char *ob_error_get_function(const ob_error *error); + +/** + * @brief Get the error parameters. + * + * @param[in] error The error object. + * @return The error parameters. + */ +OB_EXPORT const char *ob_error_get_args(const ob_error *error); + +/** + * @brief Get the type of exception that caused the error. + * + * @param[in] error The error object. + * @return The type of exception. + */ +OB_EXPORT ob_exception_type ob_error_get_exception_type(const ob_error *error); + +/** + * @brief Delete the error object. + * + * @param[in] error The error object to delete, you should set the pointer to NULL after calling this function. + */ +OB_EXPORT void ob_delete_error(ob_error *error); + +// The following interfaces are deprecated and are retained here for compatibility purposes. +#define ob_error_status ob_error_get_status +#define ob_error_message ob_error_get_message +#define ob_error_function ob_error_get_function +#define ob_error_args ob_error_get_args +#define ob_error_exception_type ob_error_get_exception_type + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Export.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Export.h new file mode 100644 index 0000000..a8fb41f --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Export.h @@ -0,0 +1,43 @@ + +#ifndef OB_EXPORT_H +#define OB_EXPORT_H + +#ifdef OB_STATIC_DEFINE +# define OB_EXPORT +# define OB_NO_EXPORT +#else +# ifndef OB_EXPORT +# ifdef OrbbecSDK_EXPORTS + /* We are building this library */ +# define OB_EXPORT __attribute__((visibility("default"))) +# else + /* We are using this library */ +# define OB_EXPORT __attribute__((visibility("default"))) +# endif +# endif + +# ifndef OB_NO_EXPORT +# define OB_NO_EXPORT __attribute__((visibility("hidden"))) +# endif +#endif + +#ifndef OB_DEPRECATED +# define OB_DEPRECATED __attribute__ ((__deprecated__)) +#endif + +#ifndef OB_DEPRECATED_EXPORT +# define OB_DEPRECATED_EXPORT OB_EXPORT OB_DEPRECATED +#endif + +#ifndef OB_DEPRECATED_NO_EXPORT +# define OB_DEPRECATED_NO_EXPORT OB_NO_EXPORT OB_DEPRECATED +#endif + +/* NOLINTNEXTLINE(readability-avoid-unconditional-preprocessor-if) */ +#if 0 /* DEFINE_NO_DEPRECATED */ +# ifndef OB_NO_DEPRECATED +# define OB_NO_DEPRECATED +# endif +#endif + +#endif /* OB_EXPORT_H */ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Filter.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Filter.h new file mode 100644 index 0000000..28e45f8 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Filter.h @@ -0,0 +1,265 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Filter.h + * @brief The processing unit of the SDK can perform point cloud generation, format conversion and other functions. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Create a Filter object. + * + * @attention If the filter of the specified name is a private filter, and the creator of the filter have not been activated, the function will return NULL. + * + * @param name The name of the filter. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT ob_filter *ob_create_filter(const char *name, ob_error **error); + +/** + * @brief Get the name of ob_filter + * + * @param filter ob_filter object + * @param error Pointer to an error object that will be set if an error occurs. + * @return char The filter of name + */ +OB_EXPORT const char *ob_filter_get_name(const ob_filter *filter, ob_error **error); + +/** + * @brief Get the vendor specific code of a filter by filter name. + * @brief A private filter can define its own vendor specific code for specific purposes. + * + * @param name The name of the filter. + * @param error Pointer to an error object that will be set if an error occurs. + * @return const char* Return the vendor specific code of the filter. + */ +OB_EXPORT const char *ob_filter_get_vendor_specific_code(const char *name, ob_error **error); + +/** + * @brief Create a private Filter object with activation key. + * @brief Some private filters require an activation key to be activated, its depends on the vendor of the filter. + * + * @param name The name of the filter. + * @param activation_key The activation key of the filter. + * @param error Pointer to an error object that will be set if an error occurs. + * + * @return ob_filter* Return the private filter object. + */ +OB_EXPORT ob_filter *ob_create_private_filter(const char *name, const char *activation_key, ob_error **error); + +/** + * @brief Delete the filter. + * + * @param[in] filter The filter object to be deleted. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_filter(ob_filter *filter, ob_error **error); + +/** + * @brief Get config schema of the filter + * @brief The returned string is a csv format string representing the configuration schema of the filter. The format of the string is: + * , , , , , , + * + * @param[in] filter The filter object to get the configuration schema for + * @param[out] error Pointer to an error object that will be set if an error occurs + * + * @return A csv format string representing the configuration schema of the filter + */ +OB_EXPORT const char *ob_filter_get_config_schema(const ob_filter *filter, ob_error **error); + +/** + * @brief Get the filter config schema list of the filter + * @brief The returned string is a list of ob_config_schema_item representing the configuration schema of the filter. + * + * @attention The returned list should be deleted by calling @ref ob_delete_filter_config_schema_list when it is no longer needed. + * + * @param filter The filter object to get the configuration schema for + * @param error Pointer to an error object that will be set if an error occurs + * @return ob_filter_config_schema_list* Return the filter config schema list of the filter + */ +OB_EXPORT ob_filter_config_schema_list *ob_filter_get_config_schema_list(const ob_filter *filter, ob_error **error); + +/** + * @brief Delete a list of filter config schema items. + * + * @param config_schema_list The list of filter config schema items to delete. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_filter_config_schema_list(ob_filter_config_schema_list *config_schema_list, ob_error **error); + +/** + * @brief Update config of the filter + * + * @attention The passed in argc and argv must match the configuration schema returned by the @ref ob_filter_get_config_schema function. + * + * @param[in] filter The filter object to update the configuration for + * @param[in] argc The number of arguments in the argv array + * @param[in] argv An array of strings representing the configuration values + * @param[out] error Pointer to an error object that will be set if an error occurs + */ +OB_EXPORT void ob_filter_update_config(ob_filter *filter, uint8_t argc, const char **argv, ob_error **error); + +/** + * @brief Get the filter config value by name and cast to double. + * + * @attention The returned value is cast to double, the actual type of the value depends on the filter config schema returned by @ref + * ob_filter_get_config_schema. + * + * @param[in] filter A filter object. + * @param[in] config_name config name + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return double The value of the config. + */ +OB_EXPORT double ob_filter_get_config_value(const ob_filter *filter, const char *config_name, ob_error **error); + +/** + * @brief Set the filter config value by name. + * + * @attention The pass into value type is double, witch will be cast to the actual type inside the filter. The actual type can be queried by the filter config + * schema returned by @ref ob_filter_get_config_schema. + * + * @param[in] filter A filter object. + * @param[in] config_name config name + * @param[in] value The value to set. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_filter_set_config_value(ob_filter *filter, const char *config_name, double value, ob_error **error); + +/** + * @brief Reset the filter, clears the cache, and resets the state. If the asynchronous interface is used, the processing thread will also be stopped and the + * pending cache frames will be cleared. + * + * @param[in] filter A filter object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_filter_reset(ob_filter *filter, ob_error **error); + +/** + * @brief Enable the frame post processing + * @brief The filter default is enable. + * + * @attention If the filter has been disabled by calling this function, processing will directly output a clone of the input frame. + * + * @param[in] filter A filter object. + * @param[in] enable enable status, true: enable; false: disable. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_filter_enable(ob_filter *filter, bool enable, ob_error **error); + +/** + * @brief Get the enable status of the frame post processing + * + * @attention If the filter is disabled, the processing will directly output a clone of the input frame. + * + * @param[in] filter A filter object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return The post processing filter status. True: enable; False: disable. + */ +OB_EXPORT bool ob_filter_is_enabled(const ob_filter *filter, ob_error **error); + +/** + * @brief Process the frame (synchronous interface). + * + * @param[in] filter A filter object. + * @param[in] frame Pointer to the frame object to be processed. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return The frame object processed by the filter. + */ +OB_EXPORT ob_frame *ob_filter_process(ob_filter *filter, const ob_frame *frame, ob_error **error); + +/** + * @brief Set the processing result callback function for the filter (asynchronous callback interface). + * + * @param[in] filter A filter object. + * @param[in] callback Callback function. + * @param[in] user_data Arbitrary user data pointer can be passed in and returned from the callback. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_filter_set_callback(ob_filter *filter, ob_filter_callback callback, void *user_data, ob_error **error); + +/** + * @brief Push the frame into the pending cache for the filter (asynchronous callback interface). + * @brief The frame will be processed by the filter when the processing thread is available and return a new processed frame to the callback function. + * + * @attention The frame object will be add reference count, so the user still need call @ref ob_delete_frame to release the frame after calling this function. + * + * @param[in] filter A filter object. + * @param[in] frame Pointer to the frame object to be processed. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_filter_push_frame(ob_filter *filter, const ob_frame *frame, ob_error **error); + +/** + * @brief Get the number of filter in the list + * + * @param[in] filter_list filter list + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t The number of list + */ +OB_EXPORT uint32_t ob_filter_list_get_count(const ob_filter_list *filter_list, ob_error **error); + +/** + * @brief Get the filter by index + * + * @param[in] filter_list Filter list + * @param[in] index Filter index + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_filter The index of ob_filter + */ +OB_EXPORT ob_filter *ob_filter_list_get_filter(const ob_filter_list *filter_list, uint32_t index, ob_error **error); + +/** + * @brief Delete a list of ob_filter objects. + * + * @param[in] filter_list The list of ob_filter objects to delete. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_filter_list(ob_filter_list *filter_list, ob_error **error); + +/** + * @brief Get the number of config schema items in the config schema list + * + * @param config_schema_list Filter config schema list + * @param error Pointer to an error object that will be set if an error occurs. + * @return uint32_t The number of config schema items in the filter list + */ +OB_EXPORT uint32_t ob_filter_config_schema_list_get_count(const ob_filter_config_schema_list *config_schema_list, ob_error **error); + +/** + * @brief Get the config schema item by index + * + * @param config_schema_list Filter config schema list + * @param index Config schema item index + * @param error Pointer to an error object that will be set if an error occurs. + * @return ob_filter_config_schema_item* The config schema item by index + */ +OB_EXPORT ob_filter_config_schema_item ob_filter_config_schema_list_get_item(const ob_filter_config_schema_list *config_schema_list, uint32_t index, + ob_error **error); + +/** + * @brief Set the align to stream profile for the align filter. + * @brief It is useful when the align target stream dose not started (without any frame to get intrinsics and extrinsics). + * + * @param filter A filter object. + * @param align_to_stream_profile The align target stream profile. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_align_filter_set_align_to_stream_profile(ob_filter *filter, const ob_stream_profile *align_to_stream_profile, ob_error **error); + +// The following interfaces are deprecated and are retained here for compatibility purposes. +#define ob_get_filter ob_filter_list_get_filter +#define ob_get_filter_name ob_filter_get_name + +#ifdef __cplusplus +} +#endif + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Frame.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Frame.h new file mode 100644 index 0000000..923e252 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Frame.h @@ -0,0 +1,657 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Frame.h + * @brief Frame related function is mainly used to obtain frame data and frame information + * + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Crate a frame object based on the specified parameters. + * + * @attention The frame object is created with a reference count of 1, and the reference count should be decreased by calling @ref ob_delete_frame() when it is + * no longer needed. + * + * @param frame_type The frame object type. + * @param format The frame object format. + * @param data_size The size of the frame object data. + * @param error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the frame object. + */ +OB_EXPORT ob_frame *ob_create_frame(ob_frame_type frame_type, ob_format format, uint32_t data_size, ob_error **error); + +/** + * @brief Create (clone) a frame object based on the specified other frame object. + * @brief The new frame object will have the same properties as the other frame object, but the data buffer is newly allocated. + * + * @attention The frame object is created with a reference count of 1, and the reference count should be decreased by calling @ref ob_delete_frame() when it is + * no longer needed. + * + * @param[in] other_frame The frame object to create the new frame object according to. + * @param[in] should_copy_data If true, the data of the source frame object will be copied to the new frame object. If false, the new frame object will + * have a data buffer with random data. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the new frame object. + */ +OB_EXPORT ob_frame *ob_create_frame_from_other_frame(const ob_frame *other_frame, bool should_copy_data, ob_error **error); + +/** + * @brief Create a frame object according to the specified stream profile. + * + * @attention The frame object is created with a reference count of 1, and the reference count should be decreased by calling @ref ob_delete_frame() when it is + * no logger needed. + * + * @param stream_profile The stream profile to create the new frame object according to. + * @param error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the new frame object. + */ +OB_EXPORT ob_frame *ob_create_frame_from_stream_profile(const ob_stream_profile *stream_profile, ob_error **error); + +/** + * @brief Create an video frame object based on the specified parameters. + * + * @attention The frame object is created with a reference count of 1, and the reference count should be decreased by calling @ref ob_delete_frame() when it is + * no longer needed. + * + * @param[in] frame_type Frame object type. + * @param[in] format Frame object format. + * @param[in] width Frame object width. + * @param[in] height Frame object height. + * @param[in] stride_bytes Row span in bytes. If 0, the stride is calculated based on the width and format. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return an empty frame object. + */ +OB_EXPORT ob_frame *ob_create_video_frame(ob_frame_type frame_type, ob_format format, uint32_t width, uint32_t height, uint32_t stride_bytes, ob_error **error); + +/** + * @brief Create a frame object based on an externally created buffer. + * + * @attention The buffer is owned by the user and will not be destroyed by the frame object. The user should ensure that the buffer is valid and not modified. + * @attention The frame object is created with a reference count of 1, and the reference count should be decreased by calling @ref ob_delete_frame() when it is + * no longer needed. + * + * @param[in] frame_type Frame object type. + * @param[in] format Frame object format. + * @param[in] buffer Frame object buffer. + * @param[in] buffer_size Frame object buffer size. + * @param[in] buffer_destroy_cb Destroy callback, will be called when the frame object is destroyed. + * @param[in] buffer_destroy_context Destroy context, user-defined context to be passed to the destroy callback. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the frame object. + */ +OB_EXPORT ob_frame *ob_create_frame_from_buffer(ob_frame_type frame_type, ob_format format, uint8_t *buffer, uint32_t buffer_size, + ob_frame_destroy_callback *buffer_destroy_cb, void *buffer_destroy_context, ob_error **error); + +/** + * @brief Create a video frame object based on an externally created buffer. + * + * @attention The buffer is owned by the user and will not be destroyed by the frame object. The user should ensure that the buffer is valid and not modified. + * @attention The frame object is created with a reference count of 1, and the reference count should be decreased by calling @ref ob_delete_frame() when it is + * no longer needed. + * + * @param[in] frame_type Frame object type. + * @param[in] format Frame object format. + * @param[in] width Frame object width. + * @param[in] height Frame object height. + * @param[in] stride_bytes Row span in bytes. If 0, the stride is calculated based on the width and format. + * @param[in] buffer Frame object buffer. + * @param[in] buffer_size Frame object buffer size. + * @param[in] buffer_destroy_cb Destroy callback, user-defined function to destroy the buffer. + * @param[in] buffer_destroy_context Destroy context, user-defined context to be passed to the destroy callback. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the frame object. + */ +OB_EXPORT ob_frame *ob_create_video_frame_from_buffer(ob_frame_type frame_type, ob_format format, uint32_t width, uint32_t height, uint32_t stride_bytes, + uint8_t *buffer, uint32_t buffer_size, ob_frame_destroy_callback *buffer_destroy_cb, + void *buffer_destroy_context, ob_error **error); + +/** + * @brief Create an empty frameset object. + * @brief A frameset object is a special type of frame object that can be used to store multiple frames. + * + * @attention The frameset object is created with a reference count of 1, and the reference count should be decreased by calling @ref ob_delete_frame() when it + * is no longer needed. + * + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the frameset object. + */ +OB_EXPORT ob_frame *ob_create_frameset(ob_error **error); + +/** + * @brief Increase the reference count of a frame object. + * @brief The reference count is used to manage the lifetime of the frame object. + * + * @attention When calling this function, the reference count of the frame object is + * increased and requires to be decreased by calling @ref ob_delete_frame(). + * + * @param[in] frame Frame object to increase the reference count. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_frame_add_ref(const ob_frame *frame, ob_error **error); + +/** + * @brief Delete a frame object + * @brief This function will decrease the reference count of the frame object and release the memory if the reference count becomes 0. + * + * @param[in] frame The frame object to delete. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_frame(const ob_frame *frame, ob_error **error); + +/** + * @brief Copy the information of the source frame object to the destination frame object. + * @brief Including the index, timestamp, system timestamp, global timestamp and metadata will be copied. + * + * @param[in] src_frame Source frame object to copy the information from. + * @param[in] dst_frame Destination frame object to copy the information to. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_frame_copy_info(const ob_frame *src_frame, ob_frame *dst_frame, ob_error **error); + +/** + * @brief Get the frame index + * + * @param[in] frame Frame object + * @param[out] error Log wrong message + * @return uint64_t return the frame index + */ +OB_EXPORT uint64_t ob_frame_get_index(const ob_frame *frame, ob_error **error); + +/** + * @brief Get the frame format + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_format return the frame format + */ +OB_EXPORT ob_format ob_frame_get_format(const ob_frame *frame, ob_error **error); + +/** + * @brief Get the frame type + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame_type return the frame type + */ +OB_EXPORT ob_frame_type ob_frame_get_type(const ob_frame *frame, ob_error **error); + +/** + * @brief Get the frame timestamp (also known as device timestamp, hardware timestamp) of the frame in microseconds. + * @brief The hardware timestamp is the time point when the frame was captured by the device (Typically in the mid-exposure, unless otherwise stated), on device + * clock domain. + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint64_t return the frame hardware timestamp in microseconds + */ +OB_EXPORT uint64_t ob_frame_get_timestamp_us(const ob_frame *frame, ob_error **error); + +/** + * @brief Set the frame timestamp (also known as the device timestamp, hardware timestamp) of a frame object. + * + * @param[in] frame Frame object to set the timestamp. + * @param[in] timestamp_us frame timestamp to set in microseconds. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_frame_set_timestamp_us(ob_frame *frame, uint64_t timestamp_us, ob_error **error); + +/** + * @brief Get the system timestamp of the frame in microseconds. + * @brief The system timestamp is the time point when the frame was received by the host, on host clock domain. + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint64_t return the frame system timestamp in microseconds + */ +OB_EXPORT uint64_t ob_frame_get_system_timestamp_us(const ob_frame *frame, ob_error **error); + +/** + * @brief Set the system timestamp of the frame in microseconds. + * + * @param frame Frame object + * @param system_timestamp_us frame system timestamp to set in microseconds. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_frame_set_system_timestamp_us(ob_frame *frame, uint64_t system_timestamp_us, ob_error **error); + +/** + * @brief Get the global timestamp of the frame in microseconds. + * @brief The global timestamp is the time point when the frame was captured by the device, and has been converted to the host clock domain. The + * conversion process base on the frame timestamp and can eliminate the timer drift of the device + * + * @attention The global timestamp is disabled by default. If global timestamp is not enabled, the function will return 0. To enable it, call @ref + * ob_device_enable_global_timestamp() function. + * @attention Only some models of device support getting the global timestamp. Check the device support status by @ref + * ob_device_is_global_timestamp_supported() function. + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint64_t The global timestamp of the frame in microseconds. + */ +OB_EXPORT uint64_t ob_frame_get_global_timestamp_us(const ob_frame *frame, ob_error **error); + +/** + * @brief Get the data buffer of a frame. + * + * @attention The returned data buffer is mutable, but it is not recommended to modify it directly. Modifying the data directly may cause issues if the frame is + * being used in other threads or future use. If you need to modify the data, it is recommended to create a new frame object. + * + * @param[in] frame The frame object from which to retrieve the data. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint8_t* Pointer to the frame data buffer. + */ +OB_EXPORT uint8_t *ob_frame_get_data(const ob_frame *frame, ob_error **error); + +/** + * @brief Update the data of a frame. + * @brief The data will be memcpy to the frame data buffer. + * @brief The frame data size will be also updated as the input data size. + * + * @attention It is not recommended to update the frame data if the frame was not created by the user. If you must update it, ensure that the frame is not being + * used in other threads. + * @attention The size of the new data should be equal to or less than the current data size of the frame. Exceeding the original size may cause memory + * exceptions. + * + * @param[in] frame The frame object to update. + * @param[in] data The new data to update the frame with. + * @param[in] data_size The size of the new data. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_frame_update_data(ob_frame *frame, const uint8_t *data, uint32_t data_size, ob_error **error); + +/** + * @brief Get the frame data size + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the frame data size + * If it is point cloud data, it return the number of bytes occupied by all point sets. If you need to find the number of points, you need to divide dataSize + * by the structure size of the corresponding point type. + */ +OB_EXPORT uint32_t ob_frame_get_data_size(const ob_frame *frame, ob_error **error); + +/** + * @brief Get the metadata of the frame + * + * @attention The returned metadata is mutable, but it is not recommended to modify it directly. Modifying the metadata directly may cause issues if the frame + * is being used in other threads or future use. If you need to modify the metadata, it is recommended to create a new frame object. + * + * @param[in] frame frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return const uint8_t * return the metadata pointer of the frame + */ +OB_EXPORT uint8_t *ob_frame_get_metadata(const ob_frame *frame, ob_error **error); +#define ob_video_frame_metadata ob_frame_get_metadata // for compatibility + +/** + * @brief Get the metadata size of the frame + * + * @param[in] frame frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the metadata size of the frame + */ +OB_EXPORT uint32_t ob_frame_get_metadata_size(const ob_frame *frame, ob_error **error); +#define ob_video_frame_metadata_size ob_frame_get_metadata_size // for compatibility + +/** + * @brief Update the metadata of the frame + * @brief The metadata will be memcpy to the frame metadata buffer. + * @brief The frame metadata size will be also updated as the input metadata size. + * + * @attention It is not recommended to update the frame metadata if the frame was not created by the user. If you must update it, ensure that the frame is not + * being used in other threads or future use. + * @attention The metadata size should be equal to or less than 256 bytes, otherwise it will cause memory exception. + * + * @param[in] frame frame object + * @param[in] metadata The new metadata to update. + * @param[in] metadata_size The size of the new metadata. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_frame_update_metadata(ob_frame *frame, const uint8_t *metadata, uint32_t metadata_size, ob_error **error); + +/** + * @brief check if the frame contains the specified metadata + * + * @param[in] frame frame object + * @param[in] type metadata type, refer to @ref ob_frame_metadata_type + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT bool ob_frame_has_metadata(const ob_frame *frame, ob_frame_metadata_type type, ob_error **error); + +/** + * @brief Get the metadata value of the frame + * + * @param[in] frame frame object + * @param[in] type metadata type, refer to @ref ob_frame_metadata_type + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return int64_t return the metadata value of the frame + */ +OB_EXPORT int64_t ob_frame_get_metadata_value(const ob_frame *frame, ob_frame_metadata_type type, ob_error **error); + +/** + * @brief Get the stream profile of the frame + * + * @attention Require @ref ob_delete_stream_profile() to release the return stream profile. + * + * @param frame frame object + * @param error Pointer to an error object that will be set if an error occurs. + * @return ob_stream_profile* Return the stream profile of the frame, if the frame is not captured by a sensor stream, it will return NULL + */ +OB_EXPORT ob_stream_profile *ob_frame_get_stream_profile(const ob_frame *frame, ob_error **error); + +/** + * @brief Set (override) the stream profile of the frame + * + * @param frame frame object + * @param stream_profile The stream profile to set for the frame. + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_frame_set_stream_profile(ob_frame *frame, const ob_stream_profile *stream_profile, ob_error **error); + +/** + * @brief Get the sensor of the frame + * + * @attention Require @ref ob_delete_sensor() to release the return sensor. + * + * @param[in] frame frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_sensor* return the sensor of the frame, if the frame is not captured by a sensor or the sensor stream has been destroyed, it will return NULL + */ +OB_EXPORT ob_sensor *ob_frame_get_sensor(const ob_frame *frame, ob_error **error); + +/** + * @brief Get the device of the frame + * + * @attention Require @ref ob_delete_device() to release the return device. + * + * @param frame frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_device* return the device of the frame, if the frame is not captured by a sensor stream or the device has been destroyed, it will return NULL + */ +OB_EXPORT ob_device *ob_frame_get_device(const ob_frame *frame, ob_error **error); + +/** + * @brief Get video frame width + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the frame width + */ +OB_EXPORT uint32_t ob_video_frame_get_width(const ob_frame *frame, ob_error **error); + +/** + * @brief Get video frame height + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the frame height + */ +OB_EXPORT uint32_t ob_video_frame_get_height(const ob_frame *frame, ob_error **error); + +/** + * @brief Get video frame pixel format + * @brief Usually used to determine the pixel type of depth frame (depth, disparity, raw phase, etc.) + * + * @attention Always return OB_PIXEL_UNKNOWN for non-depth frame currently if user has not set the pixel type by @ref ob_video_frame_set_pixel_type() + * + * @param frame Frame object + * @param error Pointer to an error object that will be set if an error occurs. + * @return ob_pixel_type return the pixel format of the frame. + */ +OB_EXPORT ob_pixel_type ob_video_frame_get_pixel_type(const ob_frame *frame, ob_error **error); + +/** + * @brief Set video frame pixel format + * + * @param frame Frame object + * @param pixel_type the pixel format of the frame + * @param error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_video_frame_set_pixel_type(ob_frame *frame, ob_pixel_type pixel_type, ob_error **error); + +/** + * @brief Get the effective number of pixels (such as Y16 format frame, but only the lower 10 bits are effective bits, and the upper 6 bits are filled with 0) + * @attention Only valid for Y8/Y10/Y11/Y12/Y14/Y16 format + * + * @param[in] frame video frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint8_t return the effective number of pixels in the pixel, or 0 if it is an unsupported format + */ +OB_EXPORT uint8_t ob_video_frame_get_pixel_available_bit_size(const ob_frame *frame, ob_error **error); + +/** + * @brief Set the effective number of pixels (such as Y16 format frame, but only the lower 10 bits are effective bits, and the upper 6 bits are filled with 0) + * @attention Only valid for Y8/Y10/Y11/Y12/Y14/Y16 format + * + * @param[in] frame video frame object + * @param[in] bit_size the effective number of pixels in the pixel, or 0 if it is an unsupported format + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_video_frame_set_pixel_available_bit_size(ob_frame *frame, uint8_t bit_size, ob_error **error); + +/** + * @brief Get the source sensor type of the ir frame (left or right for dual camera) + * + * @param frame Frame object + * @param ob_error Pointer to an error object that will be set if an error occurs. + * @return ob_sensor_type return the source sensor type of the ir frame + */ +OB_EXPORT ob_sensor_type ob_ir_frame_get_source_sensor_type(const ob_frame *frame, ob_error **ob_error); + +/** + * @brief Get the value scale of the depth frame. The pixel value of the depth frame is multiplied by the scale to give a depth value in millimeters. + * For example, if valueScale=0.1 and a certain coordinate pixel value is pixelValue=10000, then the depth value = pixelValue*valueScale = 10000*0.1=1000mm. + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return float The value scale of the depth frame + */ +OB_EXPORT float ob_depth_frame_get_value_scale(const ob_frame *frame, ob_error **error); + +/** + * @brief Set the value scale of the depth frame. The pixel value of the depth frame is multiplied by the scale to give a depth value in millimeters. + * For example, if valueScale=0.1 and a certain coordinate pixel value is pixelValue=10000, then the depth value = pixelValue*valueScale = 10000*0.1=1000mm. + * + * @param[in] frame Frame object + * @param[in] value_scale The value scale of the depth frame + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_depth_frame_set_value_scale(ob_frame *frame, float value_scale, ob_error **error); + +/** + * @brief Get the point coordinate value scale of the points frame. The point position value of the points frame is multiplied by the scale to give a position + * value in millimeters. For example, if scale=0.1, the x-coordinate value of a point is x = 10000, which means that the actual x-coordinate value = x*scale = + * 10000*0.1 = 1000mm. + * + * @param[in] frame Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return float The coordinate value scale of the points frame + */ +OB_EXPORT float ob_points_frame_get_coordinate_value_scale(const ob_frame *frame, ob_error **error); + +/** + * @brief Get accelerometer frame data. + * + * @param[in] frame Accelerometer frame. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_accel_value Return the accelerometer data. + */ +OB_EXPORT ob_accel_value ob_accel_frame_get_value(const ob_frame *frame, ob_error **error); + +/** + * @brief Get the temperature when acquiring the accelerometer frame. + * + * @param[in] frame Accelerometer frame. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return float Return the temperature value. + */ +OB_EXPORT float ob_accel_frame_get_temperature(const ob_frame *frame, ob_error **error); + +/** + * @brief Get gyroscope frame data. + * + * @param[in] frame Gyroscope frame. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_gyro_value Return the gyroscope data. + */ +OB_EXPORT ob_gyro_value ob_gyro_frame_get_value(const ob_frame *frame, ob_error **error); + +/** + * @brief Get the temperature when acquiring the gyroscope frame. + * + * @param[in] frame Gyroscope frame. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return float Return the temperature value. + */ +OB_EXPORT float ob_gyro_frame_get_temperature(const ob_frame *frame, ob_error **error); + +/** + * @brief Get the number of frames contained in the frameset + * + * @attention The frame returned by this function should call @ref ob_delete_frame() to decrease the reference count when it is no longer needed. + * + * @param[in] frameset frameset object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the number of frames + */ +OB_EXPORT uint32_t ob_frameset_get_count(const ob_frame *frameset, ob_error **error); + +/** + * @brief Get the depth frame from the frameset. + * + * @attention The frame returned by this function should call @ref ob_delete_frame() to decrease the reference count when it is no longer needed. + * + * @param[in] frameset Frameset object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the depth frame. + */ +OB_EXPORT ob_frame *ob_frameset_get_depth_frame(const ob_frame *frameset, ob_error **error); + +/** + * @brief Get the color frame from the frameset. + * + * @attention The frame returned by this function should call @ref ob_delete_frame() to decrease the reference count when it is no longer needed. + * + * @param[in] frameset Frameset object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the color frame. + */ +OB_EXPORT ob_frame *ob_frameset_get_color_frame(const ob_frame *frameset, ob_error **error); + +/** + * @brief Get the infrared frame from the frameset. + * + * @attention The frame returned by this function should call @ref ob_delete_frame() to decrease the reference count when it is no longer needed. + * + * @param[in] frameset Frameset object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the infrared frame. + */ +OB_EXPORT ob_frame *ob_frameset_get_ir_frame(const ob_frame *frameset, ob_error **error); + +/** + * @brief Get point cloud frame from the frameset. + * + * @attention The frame returned by this function should call @ref ob_delete_frame() to decrease the reference count when it is no longer needed. + * + * @param[in] frameset Frameset object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the point cloud frame. + */ +OB_EXPORT ob_frame *ob_frameset_get_points_frame(const ob_frame *frameset, ob_error **error); + +/** + * @brief Get a frame of a specific type from the frameset. + * + * @attention The frame returned by this function should call @ref ob_delete_frame() to decrease the reference count when it is no longer needed. + * + * @param[in] frameset Frameset object. + * @param[in] frame_type Frame type. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the frame of the specified type, or nullptr if it does not exist. + */ +OB_EXPORT ob_frame *ob_frameset_get_frame(const ob_frame *frameset, ob_frame_type frame_type, ob_error **error); + +/** + * @brief Get a frame at a specific index from the FrameSet + * + * @param[in] frameset Frameset object. + * @param[in] index The index of the frame. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* Return the frame at the specified index, or nullptr if it does not exist. + */ +OB_EXPORT ob_frame *ob_frameset_get_frame_by_index(const ob_frame *frameset, uint32_t index, ob_error **error); + +/** + * @brief Push a frame to the frameset + * + * @attention If a frame with same type already exists in the frameset, it will be replaced by the new frame. + * @attention The frame push to the frameset will be add reference count, so you still need to call @ref ob_delete_frame() to decrease the reference count when + * it is no longer needed. + * + * @param[in] frameset Frameset object. + * @param[in] frame Frame object to push. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_frameset_push_frame(ob_frame *frameset, const ob_frame *frame, ob_error **error); + +/** + * @brief Get point cloud frame width + * + * @param[in] frame point cloud Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the point cloud frame width + */ +OB_EXPORT uint32_t ob_point_cloud_frame_get_width(const ob_frame *frame, ob_error **error); + +/** + * @brief Get point cloud frame height + * + * @param[in] frame point cloud Frame object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the point cloud frame height + */ +OB_EXPORT uint32_t ob_point_cloud_frame_get_height(const ob_frame *frame, ob_error **error); + +// The following interfaces are deprecated and are retained here for compatibility purposes. +#define ob_frame_index ob_frame_get_index +#define ob_frame_format ob_frame_get_format +#define ob_frame_time_stamp_us ob_frame_get_timestamp_us +#define ob_frame_set_device_time_stamp_us ob_frame_set_timestamp_us +#define ob_frame_system_time_stamp_us ob_frame_get_system_timestamp_us +#define ob_frame_global_time_stamp_us ob_frame_get_global_timestamp_us +#define ob_frame_data ob_frame_get_data +#define ob_frame_data_size ob_frame_get_data_size +#define ob_frame_metadata ob_frame_get_metadata +#define ob_frame_metadata_size ob_frame_get_metadata_size +#define ob_video_frame_width ob_video_frame_get_width +#define ob_video_frame_height ob_video_frame_get_height +#define ob_video_frame_pixel_available_bit_size ob_video_frame_get_pixel_available_bit_size +#define ob_points_frame_get_position_value_scale ob_points_frame_get_coordinate_value_scale +#define ob_frameset_frame_count ob_frameset_get_count +#define ob_frameset_depth_frame ob_frameset_get_depth_frame +#define ob_frameset_color_frame ob_frameset_get_color_frame +#define ob_frameset_ir_frame ob_frameset_get_ir_frame +#define ob_frameset_points_frame ob_frameset_get_points_frame +#define ob_accel_frame_value ob_accel_frame_get_value +#define ob_accel_frame_temperature ob_accel_frame_get_temperature +#define ob_gyro_frame_value ob_gyro_frame_get_value +#define ob_gyro_frame_temperature ob_gyro_frame_get_temperature +#define ob_frameset_get_frame_count ob_frameset_get_count + +#define ob_frame_time_stamp(frame, err) (ob_frame_get_timestamp_us(frame, err) / 1000) +#define ob_frame_system_time_stamp(frame, err) (ob_frame_get_system_timestamp_us(frame, err)) +#define ob_frame_set_system_time_stamp(frame, system_timestamp, err) (ob_frame_set_system_timestamp_us(frame, system_timestamp * 1000, err)) +#define ob_frame_set_device_time_stamp(frame, device_timestamp, err) (ob_frame_set_timestamp_us(frame, device_timestamp * 1000, err)) + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/MultipleDevices.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/MultipleDevices.h new file mode 100644 index 0000000..adb0947 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/MultipleDevices.h @@ -0,0 +1,128 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file MultipleDevices.h + * @brief This file contains the multiple devices related API witch is used to control the synchronization between multiple devices and the synchronization + * between different sensor within single device. + * @brief The synchronization between multiple devices is complex, and different models have different synchronization modes and limitations. please refer to + * the product manual for details. + * @brief As the Depth and Infrared are the same sensor physically, the behavior of the Infrared is same as the Depth in the synchronization mode. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" +#include "Device.h" + +/** + * @brief Get the supported multi device sync mode bitmap of the device. + * @brief For example, if the return value is 0b00001100, it means the device supports @ref OB_MULTI_DEVICE_SYNC_MODE_PRIMARY and @ref + * OB_MULTI_DEVICE_SYNC_MODE_SECONDARY. User can check the supported mode by the code: + * ```c + * if(supported_mode_bitmap & OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN){ + * //support OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN + * } + * if(supported_mode_bitmap & OB_MULTI_DEVICE_SYNC_MODE_STANDALONE){ + * //support OB_MULTI_DEVICE_SYNC_MODE_STANDALONE + * } + * // and so on + * ``` + * @param[in] device The device handle. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint16_t return the supported multi device sync mode bitmap of the device. + */ +OB_EXPORT uint16_t ob_device_get_supported_multi_device_sync_mode_bitmap(const ob_device *device, ob_error **error); + +/** + * @brief set the multi device sync configuration of the device. + * + * @param[in] device The device handle. + * @param[in] config The multi device sync configuration. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_set_multi_device_sync_config(ob_device *device, const ob_multi_device_sync_config *config, ob_error **error); + +/** + * @brief get the current multi device sync configuration of the device. + * + * @param[in] device The device handle. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_multi_device_sync_config return the multi device sync configuration of the device. + */ +OB_EXPORT ob_multi_device_sync_config ob_device_get_multi_device_sync_config(const ob_device *device, ob_error **error); + +/** + * @brief send the capture command to the device to trigger the capture. + * @brief The device will start one time capture after receiving the capture command when it is in the @ref OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING + * + * @attention The frequency of the user call this function multiplied by the number of frames per trigger should be less than the frame rate of the stream. The + * number of frames per trigger can be set by @ref framesPerTrigger. + * @attention For some models, receive and execute the capture command will have a certain delay and performance consumption, so the frequency of calling this + * function should not be too high, please refer to the product manual for the specific supported frequency. + * @attention If the device is not in the @ref OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING mode, device will ignore the capture command. + * + * @param[in] device The device handle. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_trigger_capture(ob_device *device, ob_error **error); + +/** + * @brief set the timestamp reset configuration of the device. + * + * @param[in] device The device handle. + * @param[in] config The timestamp reset configuration. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_set_timestamp_reset_config(ob_device *device, const ob_device_timestamp_reset_config *config, ob_error **error); + +/** + * @brief get the timestamp reset configuration of the device. + * + * @param[in] device The device handle. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_device_timestamp_reset_config return the timestamp reset configuration of the device. + */ +OB_EXPORT ob_device_timestamp_reset_config ob_device_get_timestamp_reset_config(ob_device *device, ob_error **error); + +/** + * @brief send the timestamp reset command to the device. + * @brief The device will reset the timer for calculating the timestamp for output frames to 0 after receiving the timestamp reset command when the timestamp + * reset function is enabled. The timestamp reset function can be enabled by call @ref ob_device_set_timestamp_reset_config. + * + * @attention If the stream of the device is started, the timestamp of the continuous frames output by the stream will jump once after the timestamp reset. + * @attention Due to the timer of device is not high-accuracy, the timestamp of the continuous frames output by the stream will drift after a long time. User + * can call this function periodically to reset the timer to avoid the timestamp drift, the recommended interval time is 60 minutes. + * + * @param[in] device The device handle. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_timestamp_reset(ob_device *device, ob_error **error); + +/** + * @brief Alias for @ref ob_device_timestamp_reset since it is more accurate. + */ +#define ob_device_timer_reset ob_device_timestamp_reset + +/** + * @brief synchronize the timer of the device with the host. + * @brief After calling this function, the timer of the device will be synchronized with the host. User can call this function to multiple devices to + * synchronize all timers of the devices. + * + * @attention If the stream of the device is started, the timestamp of the continuous frames output by the stream will may jump once after the timer sync. + * @attention Due to the timer of device is not high-accuracy, the timestamp of the continuous frames output by the stream will drift after a long time. User + * can call this function periodically to synchronize the timer to avoid the timestamp drift, the recommended interval time is 60 minutes. + * + * @param[in] device The device handle. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_device_timer_sync_with_host(ob_device *device, ob_error **error); + +#ifdef __cplusplus +} // extern "C" +#endif + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/ObTypes.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/ObTypes.h new file mode 100644 index 0000000..ed4440b --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/ObTypes.h @@ -0,0 +1,1912 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file ObTypes.h + * @brief Provide structs commonly used in the SDK, enumerating constant definitions. + */ + +#pragma once + +#include "Export.h" + +#include +#include + +#pragma pack(push, 1) // struct 1-byte align + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ob_context_t ob_context; +typedef struct ob_device_t ob_device; +typedef struct ob_device_info_t ob_device_info; +typedef struct ob_device_list_t ob_device_list; +typedef struct ob_record_device_t ob_record_device; +typedef struct ob_playback_device_t ob_playback_device; +typedef struct ob_camera_param_list_t ob_camera_param_list; +typedef struct ob_sensor_t ob_sensor; +typedef struct ob_sensor_list_t ob_sensor_list; +typedef struct ob_stream_profile_t ob_stream_profile; +typedef struct ob_stream_profile_list_t ob_stream_profile_list; +typedef struct ob_frame_t ob_frame; +typedef struct ob_filter_t ob_filter; +typedef struct ob_filter_list_t ob_filter_list; +typedef struct ob_pipeline_t ob_pipeline; +typedef struct ob_config_t ob_config; +typedef struct ob_depth_work_mode_list_t ob_depth_work_mode_list; +typedef struct ob_device_preset_list_t ob_device_preset_list; +typedef struct ob_filter_config_schema_list_t ob_filter_config_schema_list; +typedef struct ob_device_frame_interleave_list_t ob_device_frame_interleave_list; +typedef struct ob_preset_resolution_config_list_t ob_preset_resolution_config_list; + +#define OB_WIDTH_ANY 0 +#define OB_HEIGHT_ANY 0 +#define OB_FPS_ANY 0 +#define OB_FORMAT_ANY OB_FORMAT_UNKNOWN +#define OB_PROFILE_DEFAULT 0 +#define OB_DEFAULT_STRIDE_BYTES 0 +#define OB_ACCEL_FULL_SCALE_RANGE_ANY OB_ACCEL_FS_UNKNOWN +#define OB_ACCEL_SAMPLE_RATE_ANY OB_SAMPLE_RATE_UNKNOWN +#define OB_GYRO_FULL_SCALE_RANGE_ANY OB_GYRO_FS_UNKNOWN +#define OB_GYRO_SAMPLE_RATE_ANY OB_SAMPLE_RATE_UNKNOWN + +/** + * @brief maximum path length + */ +#define OB_PATH_MAX (1024) + +/** + * @brief the permission type of api or property + */ +typedef enum { + OB_PERMISSION_DENY = 0, /**< no permission */ + OB_PERMISSION_READ = 1, /**< can read */ + OB_PERMISSION_WRITE = 2, /**< can write */ + OB_PERMISSION_READ_WRITE = 3, /**< can read and write */ + OB_PERMISSION_ANY = 255, /**< any situation above */ +} OBPermissionType, + ob_permission_type; + +/** + * @brief error code + */ +typedef enum { + OB_STATUS_OK = 0, /**< status ok */ + OB_STATUS_ERROR = 1, /**< status error */ +} OBStatus, + ob_status; + +/** + * @brief log level, the higher the level, the stronger the log filter + */ +typedef enum { + OB_LOG_SEVERITY_DEBUG, /**< debug */ + OB_LOG_SEVERITY_INFO, /**< information */ + OB_LOG_SEVERITY_WARN, /**< warning */ + OB_LOG_SEVERITY_ERROR, /**< error */ + OB_LOG_SEVERITY_FATAL, /**< fatal error */ + OB_LOG_SEVERITY_OFF /**< off (close LOG) */ +} OBLogSeverity, + ob_log_severity, DEVICE_LOG_SEVERITY_LEVEL, OBDeviceLogSeverityLevel, ob_device_log_severity_level; +#define OB_LOG_SEVERITY_NONE OB_LOG_SEVERITY_OFF + +/** + * @brief The exception types in the SDK, through the exception type, you can easily determine the specific type of error. + * For detailed error API interface functions and error logs, please refer to the information of ob_error + */ +typedef enum { + OB_EXCEPTION_TYPE_UNKNOWN, /**< Unknown error, an error not clearly defined by the SDK */ + OB_EXCEPTION_STD_EXCEPTION, /** < Standard exception, an error caused by the standard library */ + OB_EXCEPTION_TYPE_CAMERA_DISCONNECTED, /**< Camera/Device has been disconnected, the camera/device is not available */ + OB_EXCEPTION_TYPE_PLATFORM, /**< An error in the SDK adaptation platform layer, which means an error in the implementation of a specific system + platform */ + OB_EXCEPTION_TYPE_INVALID_VALUE, /**< Invalid parameter type exception, need to check input parameter */ + OB_EXCEPTION_TYPE_WRONG_API_CALL_SEQUENCE, /**< Wrong API call sequence, the API is called in the wrong order or the wrong parameter is passed */ + OB_EXCEPTION_TYPE_NOT_IMPLEMENTED, /**< SDK and firmware have not yet implemented this function or feature */ + OB_EXCEPTION_TYPE_IO, /**< SDK access IO exception error */ + OB_EXCEPTION_TYPE_MEMORY, /**< SDK access and use memory errors. For example, the frame fails to allocate memory */ + OB_EXCEPTION_TYPE_UNSUPPORTED_OPERATION, /**< Unsupported operation type error by SDK or device */ +} OBExceptionType, + ob_exception_type; + +/** + * @brief The error class exposed by the SDK, users can get detailed error information according to the error + */ +typedef struct ob_error { + ob_status status; ///< Describe the status code of the error, as compatible with previous customer status code requirements + char message[256]; ///< Describe the detailed error log + char function[256]; ///< Describe the name of the function where the error occurred + char args[256]; ///< Describes the parameters passed to the function when an error occurs. Used to check whether the parameter is wrong + ob_exception_type exception_type; ///< The description is the specific error type of the SDK +} ob_error; + +/** + * @brief Enumeration value describing the sensor type + */ +typedef enum { + OB_SENSOR_UNKNOWN = 0, /**< Unknown type sensor */ + OB_SENSOR_IR = 1, /**< IR */ + OB_SENSOR_COLOR = 2, /**< Color */ + OB_SENSOR_DEPTH = 3, /**< Depth */ + OB_SENSOR_ACCEL = 4, /**< Accel */ + OB_SENSOR_GYRO = 5, /**< Gyro */ + OB_SENSOR_IR_LEFT = 6, /**< left IR for stereo camera*/ + OB_SENSOR_IR_RIGHT = 7, /**< Right IR for stereo camera*/ + OB_SENSOR_RAW_PHASE = 8, /**< Raw Phase */ + OB_SENSOR_CONFIDENCE = 9, /**< Confidence */ + OB_SENSOR_TYPE_COUNT, /**The total number of sensor types, is not a valid sensor type */ +} OBSensorType, + ob_sensor_type; + +/** + * @brief Enumeration value describing the type of data stream + */ +typedef enum { + OB_STREAM_UNKNOWN = -1, /**< Unknown type stream */ + OB_STREAM_VIDEO = 0, /**< Video stream (infrared, color, depth streams are all video streams) */ + OB_STREAM_IR = 1, /**< IR stream */ + OB_STREAM_COLOR = 2, /**< color stream */ + OB_STREAM_DEPTH = 3, /**< depth stream */ + OB_STREAM_ACCEL = 4, /**< Accelerometer data stream */ + OB_STREAM_GYRO = 5, /**< Gyroscope data stream */ + OB_STREAM_IR_LEFT = 6, /**< Left IR stream for stereo camera */ + OB_STREAM_IR_RIGHT = 7, /**< Right IR stream for stereo camera */ + OB_STREAM_RAW_PHASE = 8, /**< RawPhase Stream */ + OB_STREAM_CONFIDENCE = 9, /**< Confidence Stream*/ + OB_STREAM_TYPE_COUNT, /**< The total number of stream type,is not a valid stream type */ +} OBStreamType, + ob_stream_type; + +/** + * @brief Enumeration value describing the type of frame + */ +typedef enum { + OB_FRAME_UNKNOWN = -1, /**< Unknown frame type */ + OB_FRAME_VIDEO = 0, /**< Video frame */ + OB_FRAME_IR = 1, /**< IR frame */ + OB_FRAME_COLOR = 2, /**< Color frame */ + OB_FRAME_DEPTH = 3, /**< Depth frame */ + OB_FRAME_ACCEL = 4, /**< Accelerometer data frame */ + OB_FRAME_SET = 5, /**< Frame collection (internally contains a variety of data frames) */ + OB_FRAME_POINTS = 6, /**< Point cloud frame */ + OB_FRAME_GYRO = 7, /**< Gyroscope data frame */ + OB_FRAME_IR_LEFT = 8, /**< Left IR frame for stereo camera */ + OB_FRAME_IR_RIGHT = 9, /**< Right IR frame for stereo camera */ + OB_FRAME_RAW_PHASE = 10, /**< Raw Phase frame*/ + OB_FRAME_CONFIDENCE = 11, /**< Confidence frame*/ + OB_FRAME_TYPE_COUNT, /**< The total number of frame types, is not a valid frame type */ +} OBFrameType, + ob_frame_type; + +/** + * @brief Enumeration value describing the pixel type of frame (usually used for depth frame) + * + */ +typedef enum { + OB_PIXEL_UNKNOWN = -1, // Unknown pixel type, or undefined pixel type for current frame + OB_PIXEL_DEPTH = 0, // Depth pixel type, the value of the pixel is the distance from the camera to the object + OB_PIXEL_DISPARITY = 2, // Disparity for structured light camera + OB_PIXEL_RAW_PHASE = 3, // Raw phase for tof camera + OB_PIXEL_TOF_DEPTH = 4, // Depth for tof camera +} OBPixelType, + ob_pixel_type; + +/** + * @brief Enumeration value describing the pixel format + */ +typedef enum { + OB_FORMAT_UNKNOWN = -1, /**< unknown format */ + OB_FORMAT_YUYV = 0, /**< YUYV format */ + OB_FORMAT_YUY2 = 1, /**< YUY2 format (the actual format is the same as YUYV) */ + OB_FORMAT_UYVY = 2, /**< UYVY format */ + OB_FORMAT_NV12 = 3, /**< NV12 format */ + OB_FORMAT_NV21 = 4, /**< NV21 format */ + OB_FORMAT_MJPG = 5, /**< MJPEG encoding format */ + OB_FORMAT_H264 = 6, /**< H.264 encoding format */ + OB_FORMAT_H265 = 7, /**< H.265 encoding format */ + OB_FORMAT_Y16 = 8, /**< Y16 format, 16-bit per pixel, single-channel*/ + OB_FORMAT_Y8 = 9, /**< Y8 format, 8-bit per pixel, single-channel */ + OB_FORMAT_Y10 = 10, /**< Y10 format, 10-bit per pixel, single-channel(SDK will unpack into Y16 by default) */ + OB_FORMAT_Y11 = 11, /**< Y11 format, 11-bit per pixel, single-channel (SDK will unpack into Y16 by default) */ + OB_FORMAT_Y12 = 12, /**< Y12 format, 12-bit per pixel, single-channel(SDK will unpack into Y16 by default) */ + OB_FORMAT_GRAY = 13, /**< GRAY (the actual format is the same as YUYV) */ + OB_FORMAT_HEVC = 14, /**< HEVC encoding format (the actual format is the same as H265) */ + OB_FORMAT_I420 = 15, /**< I420 format */ + OB_FORMAT_ACCEL = 16, /**< Acceleration data format */ + OB_FORMAT_GYRO = 17, /**< Gyroscope data format */ + OB_FORMAT_POINT = 19, /**< XYZ 3D coordinate point format, @ref OBPoint */ + OB_FORMAT_RGB_POINT = 20, /**< XYZ 3D coordinate point format with RGB information, @ref OBColorPoint */ + OB_FORMAT_RLE = 21, /**< RLE pressure test format (SDK will be unpacked into Y16 by default) */ + OB_FORMAT_RGB = 22, /**< RGB format (actual RGB888) */ + OB_FORMAT_BGR = 23, /**< BGR format (actual BGR888) */ + OB_FORMAT_Y14 = 24, /**< Y14 format, 14-bit per pixel, single-channel (SDK will unpack into Y16 by default) */ + OB_FORMAT_BGRA = 25, /**< BGRA format */ + OB_FORMAT_COMPRESSED = 26, /**< Compression format */ + OB_FORMAT_RVL = 27, /**< RVL pressure test format (SDK will be unpacked into Y16 by default) */ + OB_FORMAT_Z16 = 28, /**< Is same as Y16*/ + OB_FORMAT_YV12 = 29, /**< Is same as Y12, using for right ir stream*/ + OB_FORMAT_BA81 = 30, /**< Is same as Y8, using for right ir stream*/ + OB_FORMAT_RGBA = 31, /**< RGBA format */ + OB_FORMAT_BYR2 = 32, /**< byr2 format */ + OB_FORMAT_RW16 = 33, /**< RAW16 format */ + OB_FORMAT_Y12C4 = 34, /**< Y12C4 format */ +} OBFormat, + ob_format; + +#define OB_FORMAT_RGB888 OB_FORMAT_RGB // Alias of OB_FORMAT_RGB for compatibility +#define OB_FORMAT_MJPEG OB_FORMAT_MJPG // Alias of OB_FORMAT_MJPG for compatibility + +// Check if the format is a fixed data size format +#define IS_FIXED_SIZE_FORMAT(format) \ + (format != OB_FORMAT_MJPG && format != OB_FORMAT_H264 && format != OB_FORMAT_H265 && format != OB_FORMAT_HEVC && format != OB_FORMAT_RLE \ + && format != OB_FORMAT_RVL) + +// Check if the format is a packed format, which means the data of pixels is not continuous or bytes aligned in memory +#define IS_PACKED_FORMAT(format) \ + (format == OB_FORMAT_Y10 || format == OB_FORMAT_Y11 || format == OB_FORMAT_Y12 || format == OB_FORMAT_Y14 || format == OB_FORMAT_RLE) + +/** + * @brief Enumeration value describing the firmware upgrade status + */ +typedef enum { + STAT_DONE_WITH_DUPLICATES = 6, /**< update completed, but some files were duplicated and ignored */ + STAT_VERIFY_SUCCESS = 5, /**< Image file verifify success */ + STAT_FILE_TRANSFER = 4, /**< file transfer */ + STAT_DONE = 3, /**< update completed */ + STAT_IN_PROGRESS = 2, /**< upgrade in process */ + STAT_START = 1, /**< start the upgrade */ + STAT_VERIFY_IMAGE = 0, /**< Image file verification */ + ERR_VERIFY = -1, /**< Verification failed */ + ERR_PROGRAM = -2, /**< Program execution failed */ + ERR_ERASE = -3, /**< Flash parameter failed */ + ERR_FLASH_TYPE = -4, /**< Flash type error */ + ERR_IMAGE_SIZE = -5, /**< Image file size error */ + ERR_OTHER = -6, /**< other errors */ + ERR_DDR = -7, /**< DDR access error */ + ERR_TIMEOUT = -8, /**< timeout error */ + ERR_MISMATCH = -9, /**< Mismatch firmware error */ + ERR_UNSUPPORT_DEV = -10, /**< Unsupported device error */ + ERR_INVALID_COUNT = -11, /**< invalid firmware/preset count */ +} OBUpgradeState, + OBFwUpdateState, ob_upgrade_state, ob_fw_update_state; + +/** + * @brief Enumeration value describing the file transfer status + */ +typedef enum { + FILE_TRAN_STAT_TRANSFER = 2, /**< File transfer */ + FILE_TRAN_STAT_DONE = 1, /**< File transfer succeeded */ + FILE_TRAN_STAT_PREPAR = 0, /**< Preparing */ + FILE_TRAN_ERR_DDR = -1, /**< DDR access failed */ + FILE_TRAN_ERR_NOT_ENOUGH_SPACE = -2, /**< Insufficient target space error */ + FILE_TRAN_ERR_PATH_NOT_WRITABLE = -3, /**< Destination path is not writable */ + FILE_TRAN_ERR_MD5_ERROR = -4, /**< MD5 checksum error */ + FILE_TRAN_ERR_WRITE_FLASH_ERROR = -5, /**< Write flash error */ + FILE_TRAN_ERR_TIMEOUT = -6 /**< Timeout error */ +} OBFileTranState, + ob_file_tran_state; + +/** + * @brief Enumeration value describing the data transfer status + */ +typedef enum { + DATA_TRAN_STAT_VERIFY_DONE = 4, /**< data verify done */ + DATA_TRAN_STAT_STOPPED = 3, /**< data transfer stoped */ + DATA_TRAN_STAT_DONE = 2, /**< data transfer completed */ + DATA_TRAN_STAT_VERIFYING = 1, /**< data verifying */ + DATA_TRAN_STAT_TRANSFERRING = 0, /**< data transferring */ + DATA_TRAN_ERR_BUSY = -1, /**< Transmission is busy */ + DATA_TRAN_ERR_UNSUPPORTED = -2, /**< Not supported */ + DATA_TRAN_ERR_TRAN_FAILED = -3, /**< Transfer failed */ + DATA_TRAN_ERR_VERIFY_FAILED = -4, /**< Test failed */ + DATA_TRAN_ERR_OTHER = -5 /**< Other errors */ +} OBDataTranState, + ob_data_tran_state; + +/** + * @brief Structure for transmitting data blocks + */ +typedef struct { + uint8_t *data; ///< Pointer to current block data + uint32_t size; ///< Length of current block data + uint32_t offset; ///< Offset of current data block relative to complete data + uint32_t fullDataSize; ///< Size of full data +} OBDataChunk, ob_data_chunk; + +/** + * @brief Structure for integer range + */ +typedef struct { + int32_t cur; ///< Current value + int32_t max; ///< Maximum value + int32_t min; ///< Minimum value + int32_t step; ///< Step value + int32_t def; ///< Default value +} OBIntPropertyRange, ob_int_property_range; + +/** + * @brief Structure for float range + */ +typedef struct { + float cur; ///< Current value + float max; ///< Maximum value + float min; ///< Minimum value + float step; ///< Step value + float def; ///< Default value +} OBFloatPropertyRange, ob_float_property_range; + +/** + * @brief Structure for float range + */ +typedef struct { + uint16_t cur; ///< Current value + uint16_t max; ///< Maximum value + uint16_t min; ///< Minimum value + uint16_t step; ///< Step value + uint16_t def; ///< Default value +} OBUint16PropertyRange, ob_uint16_property_range; + +/** + * @brief Structure for float range + */ +typedef struct { + uint8_t cur; ///< Current value + uint8_t max; ///< Maximum value + uint8_t min; ///< Minimum value + uint8_t step; ///< Step value + uint8_t def; ///< Default value +} OBUint8PropertyRange, ob_uint8_property_range; + +/** + * @brief Structure for boolean range + */ +typedef struct { + bool cur; ///< Current value + bool max; ///< Maximum value + bool min; ///< Minimum value + bool step; ///< Step value + bool def; ///< Default value +} OBBoolPropertyRange, ob_bool_property_range; + +/** \brief Distortion model: defines how pixel coordinates should be mapped to sensor coordinates. */ +typedef enum { + OB_DISTORTION_NONE, /**< Rectilinear images. No distortion compensation required. */ + OB_DISTORTION_MODIFIED_BROWN_CONRADY, /**< Equivalent to Brown-Conrady distortion, except that tangential distortion is applied to radially distorted points + */ + OB_DISTORTION_INVERSE_BROWN_CONRADY, /**< Equivalent to Brown-Conrady distortion, except undistorts image instead of distorting it */ + OB_DISTORTION_BROWN_CONRADY, /**< Unmodified Brown-Conrady distortion model */ + OB_DISTORTION_BROWN_CONRADY_K6, /**< Unmodified Brown-Conrady distortion model with k6 supported */ + OB_DISTORTION_KANNALA_BRANDT4, /**< Kannala-Brandt distortion model */ +} OBCameraDistortionModel, + ob_camera_distortion_model; + +/** + * @brief Structure for camera intrinsic parameters + */ +typedef struct { + float fx; ///< Focal length in x direction + float fy; ///< Focal length in y direction + float cx; ///< Optical center abscissa + float cy; ///< Optical center ordinate + int16_t width; ///< Image width + int16_t height; ///< Image height +} OBCameraIntrinsic, ob_camera_intrinsic; + +/** + * @brief Structure for accelerometer intrinsic parameters + */ +typedef struct { + double noiseDensity; ///< In-run bias instability + double randomWalk; ///< random walk + double referenceTemp; ///< reference temperature + double bias[3]; ///< bias for x, y, z axis + double gravity[3]; ///< gravity direction for x, y, z axis + double scaleMisalignment[9]; ///< scale factor and three-axis non-orthogonal error + double tempSlope[9]; ///< linear temperature drift coefficient +} OBAccelIntrinsic, ob_accel_intrinsic; + +/** + * @brief Structure for gyroscope intrinsic parameters + */ +typedef struct { + double noiseDensity; ///< In-run bias instability + double randomWalk; ///< random walk + double referenceTemp; ///< reference temperature + double bias[3]; ///< bias for x, y, z axis + double scaleMisalignment[9]; ///< scale factor and three-axis non-orthogonal error + double tempSlope[9]; ///< linear temperature drift coefficient +} OBGyroIntrinsic, ob_gyro_intrinsic; + +/** + * @brief Structure for distortion parameters + */ +typedef struct { + float k1; ///< Radial distortion factor 1 + float k2; ///< Radial distortion factor 2 + float k3; ///< Radial distortion factor 3 + float k4; ///< Radial distortion factor 4 + float k5; ///< Radial distortion factor 5 + float k6; ///< Radial distortion factor 6 + float p1; ///< Tangential distortion factor 1 + float p2; ///< Tangential distortion factor 2 + OBCameraDistortionModel model; +} OBCameraDistortion, ob_camera_distortion; + +/** + * @brief Structure for rotation/transformation + */ +typedef struct { + float rot[9]; ///< Rotation matrix + float trans[3]; ///< Transformation matrix in millimeters +} OBD2CTransform, ob_d2c_transform, OBTransform, ob_transform, OBExtrinsic, ob_extrinsic; + +/** + * @brief Structure for camera parameters + */ +typedef struct { + OBCameraIntrinsic depthIntrinsic; ///< Depth camera internal parameters + OBCameraIntrinsic rgbIntrinsic; ///< Color camera internal parameters + OBCameraDistortion depthDistortion; ///< Depth camera distortion parameters + OBCameraDistortion rgbDistortion; ///< Color camera distortion parameters + OBD2CTransform transform; ///< Rotation/transformation matrix + bool isMirrored; ///< Whether the image frame corresponding to this group of parameters is mirrored +} OBCameraParam, ob_camera_param; + +typedef struct { + int16_t width; ///< width + int16_t height; ///< height + int irDecimationFactor; ///< ir decimation factor + int depthDecimationFactor; ///< depth decimation factor +} OBPresetResolutionConfig, ob_preset_resolution_ratio_config; + +/** + * @brief calibration parameters + */ +typedef struct { + OBCameraIntrinsic intrinsics[OB_SENSOR_TYPE_COUNT]; ///< Sensor internal parameters + OBCameraDistortion distortion[OB_SENSOR_TYPE_COUNT]; ///< Sensor distortion + OBExtrinsic extrinsics[OB_SENSOR_TYPE_COUNT] + [OB_SENSOR_TYPE_COUNT]; ///< The extrinsic parameters allow 3D coordinate conversions between sensor.To transform from a + ///< source to a target 3D coordinate system,under extrinsics[source][target]. +} OBCalibrationParam, ob_calibration_param; + +/** + * @brief Configuration for depth margin filter + */ +typedef struct { + int margin_x_th; ///< Horizontal threshold settings + int margin_y_th; ///< Vertical threshold settings + int limit_x_th; ///< Maximum horizontal threshold + int limit_y_th; ///< Maximum vertical threshold + uint32_t width; ///< Image width + uint32_t height; ///< Image height + bool enable_direction; ///< Set to true for horizontal and vertical, false for horizontal only +} ob_margin_filter_config, OBMarginFilterConfig; + +/** + * @brief Configuration for mgc filter + */ +typedef struct { + uint32_t width; + uint32_t height; + int max_width_left; + int max_width_right; + int max_radius; + int margin_x_th; + int margin_y_th; + int limit_x_th; + int limit_y_th; +} OBMGCFilterConfig, ob_mgc_filter_config; + +/** + * @brief Alignment mode + */ +typedef enum { + ALIGN_DISABLE, /**< Turn off alignment */ + ALIGN_D2C_HW_MODE, /**< Hardware D2C alignment mode */ + ALIGN_D2C_SW_MODE, /**< Software D2C alignment mode */ +} OBAlignMode, + ob_align_mode; + +/** + * @brief Camera performance mode + */ +typedef enum { + ADAPTIVE_PERFORMANCE_MODE, /**< Camera adaptive mode */ + HIGH_PERFORMANCE_MODE, /**< High Performance Mode */ +} OBCameraPerformanceMode, + ob_camera_performance_mode; + +/** + * @brief Rectangle + */ +typedef struct { + uint32_t x; ///< Origin coordinate x + uint32_t y; ///< Origin coordinate y + uint32_t width; ///< Rectangle width + uint32_t height; ///< Rectangle height +} OBRect, ob_rect; + +/** + * @brief Enumeration of format conversion types + */ +typedef enum { + FORMAT_YUYV_TO_RGB = 0, /**< YUYV to RGB */ + FORMAT_I420_TO_RGB, /**< I420 to RGB */ + FORMAT_NV21_TO_RGB, /**< NV21 to RGB */ + FORMAT_NV12_TO_RGB, /**< NV12 to RGB */ + FORMAT_MJPG_TO_I420, /**< MJPG to I420 */ + FORMAT_RGB_TO_BGR, /**< RGB888 to BGR */ + FORMAT_MJPG_TO_NV21, /**< MJPG to NV21 */ + FORMAT_MJPG_TO_RGB, /**< MJPG to RGB */ + FORMAT_MJPG_TO_BGR, /**< MJPG to BGR */ + FORMAT_MJPG_TO_BGRA, /**< MJPG to BGRA */ + FORMAT_UYVY_TO_RGB, /**< UYVY to RGB */ + FORMAT_BGR_TO_RGB, /**< BGR to RGB */ + FORMAT_MJPG_TO_NV12, /**< MJPG to NV12 */ + FORMAT_YUYV_TO_BGR, /**< YUYV to BGR */ + FORMAT_YUYV_TO_RGBA, /**< YUYV to RGBA */ + FORMAT_YUYV_TO_BGRA, /**< YUYV to BGRA */ + FORMAT_YUYV_TO_Y16, /**< YUYV to Y16 */ + FORMAT_YUYV_TO_Y8, /**< YUYV to Y8 */ + FORMAT_RGBA_TO_RGB, /**< RGBA to RGB */ + FORMAT_BGRA_TO_BGR, /**< BGRA to BGR */ + FORMAT_Y16_TO_RGB, /**< Y16 to RGB */ + FORMAT_Y8_TO_RGB, /**< Y8 to RGB */ +} OBConvertFormat, + ob_convert_format; + +// DEPRECATED: Only used for old version program compatibility, will be completely deleted in subsequent iterative versions +#define FORMAT_MJPEG_TO_I420 FORMAT_MJPG_TO_I420 +#define FORMAT_MJPEG_TO_NV21 FORMAT_MJPG_TO_NV21 +#define FORMAT_MJPEG_TO_BGRA FORMAT_MJPG_TO_BGRA +#define FORMAT_YUYV_TO_RGB888 FORMAT_YUYV_TO_RGB +#define FORMAT_I420_TO_RGB888 FORMAT_I420_TO_RGB +#define FORMAT_NV21_TO_RGB888 FORMAT_NV21_TO_RGB +#define FORMAT_NV12_TO_RGB888 FORMAT_NV12_TO_RGB +#define FORMAT_UYVY_TO_RGB888 FORMAT_UYVY_TO_RGB +#define FORMAT_MJPG_TO_RGB888 FORMAT_MJPG_TO_RGB +#define FORMAT_MJPG_TO_BGR888 FORMAT_MJPG_TO_BGR +#define FORMAT_MJPEG_TO_RGB888 FORMAT_MJPG_TO_RGB +#define FORMAT_MJPEG_TO_BGR888 FORMAT_MJPG_TO_BGR +#define FORMAT_RGB888_TO_BGR FORMAT_RGB_TO_BGR + +/** + * @brief Enumeration of IMU sample rate values (gyroscope or accelerometer) + */ +typedef enum { + OB_SAMPLE_RATE_UNKNOWN = 0, + OB_SAMPLE_RATE_1_5625_HZ = 1, /**< 1.5625Hz */ + OB_SAMPLE_RATE_3_125_HZ = 2, /**< 3.125Hz */ + OB_SAMPLE_RATE_6_25_HZ = 3, /**< 6.25Hz */ + OB_SAMPLE_RATE_12_5_HZ = 4, /**< 12.5Hz */ + OB_SAMPLE_RATE_25_HZ = 5, /**< 25Hz */ + OB_SAMPLE_RATE_50_HZ = 6, /**< 50Hz */ + OB_SAMPLE_RATE_100_HZ = 7, /**< 100Hz */ + OB_SAMPLE_RATE_200_HZ = 8, /**< 200Hz */ + OB_SAMPLE_RATE_500_HZ = 9, /**< 500Hz */ + OB_SAMPLE_RATE_1_KHZ = 10, /**< 1KHz */ + OB_SAMPLE_RATE_2_KHZ = 11, /**< 2KHz */ + OB_SAMPLE_RATE_4_KHZ = 12, /**< 4KHz */ + OB_SAMPLE_RATE_8_KHZ = 13, /**< 8KHz */ + OB_SAMPLE_RATE_16_KHZ = 14, /**< 16KHz */ + OB_SAMPLE_RATE_32_KHZ = 15, /**< 32Hz */ + OB_SAMPLE_RATE_400_HZ = 16, /**< 400Hz*/ + OB_SAMPLE_RATE_800_HZ = 17, /**< 800Hz*/ +} OBIMUSampleRate, + OBGyroSampleRate, ob_gyro_sample_rate, OBAccelSampleRate, ob_accel_sample_rate, OB_SAMPLE_RATE; + +/** + * @brief Enumeration of gyroscope ranges + */ +typedef enum { + OB_GYRO_FS_UNKNOWN = -1, + OB_GYRO_FS_16dps = 1, /**< 16 degrees per second */ + OB_GYRO_FS_31dps = 2, /**< 31 degrees per second */ + OB_GYRO_FS_62dps = 3, /**< 62 degrees per second */ + OB_GYRO_FS_125dps = 4, /**< 125 degrees per second */ + OB_GYRO_FS_250dps = 5, /**< 250 degrees per second */ + OB_GYRO_FS_500dps = 6, /**< 500 degrees per second */ + OB_GYRO_FS_1000dps = 7, /**< 1000 degrees per second */ + OB_GYRO_FS_2000dps = 8, /**< 2000 degrees per second */ + OB_GYRO_FS_400dps = 9, /**< 400 degrees per second */ + OB_GYRO_FS_800dps = 10, /**< 800 degrees per second */ +} OBGyroFullScaleRange, + ob_gyro_full_scale_range, OB_GYRO_FULL_SCALE_RANGE; + +/** + * @brief Enumeration of accelerometer ranges + */ +typedef enum { + OB_ACCEL_FS_UNKNOWN = -1, + OB_ACCEL_FS_2g = 1, /**< 1x the acceleration of gravity */ + OB_ACCEL_FS_4g = 2, /**< 4x the acceleration of gravity */ + OB_ACCEL_FS_8g = 3, /**< 8x the acceleration of gravity */ + OB_ACCEL_FS_16g = 4, /**< 16x the acceleration of gravity */ + OB_ACCEL_FS_3g = 5, /**< 3x the acceleration of gravity */ + OB_ACCEL_FS_6g = 6, /**< 6x the acceleration of gravity */ + OB_ACCEL_FS_12g = 7, /**< 12x the acceleration of gravity */ + OB_ACCEL_FS_24g = 8, /**< 24x the acceleration of gravity */ +} OBAccelFullScaleRange, + ob_accel_full_scale_range, OB_ACCEL_FULL_SCALE_RANGE; + +/** + * @brief Data structures for accelerometers and gyroscopes + */ +typedef struct { + float x; ///< X-direction component + float y; ///< Y-direction component + float z; ///< Z-direction component +} OBAccelValue, OBGyroValue, OBFloat3D, ob_accel_value, ob_gyro_value, ob_float_3d; + +/** + * @brief Device state + */ +typedef uint64_t OBDeviceState, ob_device_state; + +/** + * @brief Temperature parameters of the device (unit: Celsius) + */ +typedef struct { + float cpuTemp; ///< CPU temperature + float irTemp; ///< IR temperature + float ldmTemp; ///< Laser temperature + float mainBoardTemp; ///< Motherboard temperature + float tecTemp; ///< TEC temperature + float imuTemp; ///< IMU temperature + float rgbTemp; ///< RGB temperature + float irLeftTemp; ///< Left IR temperature + float irRightTemp; ///< Right IR temperature + float chipTopTemp; ///< MX6600 top temperature + float chipBottomTemp; ///< MX6600 bottom temperature +} OBDeviceTemperature, ob_device_temperature, DEVICE_TEMPERATURE; + +/** + * @brief Enumeration for depth crop modes + */ +typedef enum { + DEPTH_CROPPING_MODE_AUTO = 0, /**< Automatic mode */ + DEPTH_CROPPING_MODE_CLOSE = 1, /**< Close crop */ + DEPTH_CROPPING_MODE_OPEN = 2, /**< Open crop */ +} OBDepthCroppingMode, + ob_depth_cropping_mode, OB_DEPTH_CROPPING_MODE; + +/** + * @brief Enumeration for device types + */ +typedef enum { + OB_DEVICE_TYPE_UNKNOWN = -1, /**< Unknown device type */ + OB_STRUCTURED_LIGHT_MONOCULAR_CAMERA = 0, /**< Monocular structured light camera */ + OB_STRUCTURED_LIGHT_BINOCULAR_CAMERA = 1, /**< Binocular structured light camera */ + OB_TOF_CAMERA = 2, /**< Time-of-flight camera */ +} OBDeviceType, + ob_device_type, OB_DEVICE_TYPE; + +/** + * @brief Enumeration for types of media to record or playback + */ +typedef enum { + OB_MEDIA_COLOR_STREAM = 1, /**< Color stream */ + OB_MEDIA_DEPTH_STREAM = 2, /**< Depth stream */ + OB_MEDIA_IR_STREAM = 4, /**< Infrared stream */ + OB_MEDIA_GYRO_STREAM = 8, /**< Gyroscope stream */ + OB_MEDIA_ACCEL_STREAM = 16, /**< Accelerometer stream */ + OB_MEDIA_CAMERA_PARAM = 32, /**< Camera parameter */ + OB_MEDIA_DEVICE_INFO = 64, /**< Device information */ + OB_MEDIA_STREAM_INFO = 128, /**< Stream information */ + OB_MEDIA_IR_LEFT_STREAM = 256, /**< Left infrared stream */ + OB_MEDIA_IR_RIGHT_STREAM = 512, /**< Right infrared stream */ + + OB_MEDIA_ALL = OB_MEDIA_COLOR_STREAM | OB_MEDIA_DEPTH_STREAM | OB_MEDIA_IR_STREAM | OB_MEDIA_GYRO_STREAM | OB_MEDIA_ACCEL_STREAM | OB_MEDIA_CAMERA_PARAM + | OB_MEDIA_DEVICE_INFO | OB_MEDIA_STREAM_INFO | OB_MEDIA_IR_LEFT_STREAM | OB_MEDIA_IR_RIGHT_STREAM, /**< All media data types */ +} OBMediaType, + ob_media_type, OB_MEDIA_TYPE; + +/** + * @brief Enumeration for record playback status + */ +typedef enum { + OB_MEDIA_BEGIN = 0, /**< Begin */ + OB_MEDIA_PAUSE, /**< Pause */ + OB_MEDIA_RESUME, /**< Resume */ + OB_MEDIA_END, /**< End */ +} OBMediaState, + ob_media_state, OB_MEDIA_STATE_EM; + +/** + * @brief Enumeration for depth precision levels + * @attention The depth precision level does not completely determine the depth unit and real precision, and the influence of the data packaging format needs to + * be considered. The specific unit can be obtained through getValueScale() of DepthFrame + */ +typedef enum { + OB_PRECISION_1MM, /**< 1mm */ + OB_PRECISION_0MM8, /**< 0.8mm */ + OB_PRECISION_0MM4, /**< 0.4mm */ + OB_PRECISION_0MM1, /**< 0.1mm */ + OB_PRECISION_0MM2, /**< 0.2mm */ + OB_PRECISION_0MM5, /**< 0.5mm */ + OB_PRECISION_0MM05, /**< 0.05mm */ + OB_PRECISION_UNKNOWN, + OB_PRECISION_COUNT, +} OBDepthPrecisionLevel, + ob_depth_precision_level, OB_DEPTH_PRECISION_LEVEL, OBDepthUnit, ob_depth_unit; + +/** + * @brief disparity parameters for disparity based camera + * + */ +typedef struct { + double zpd; // the distance to calib plane + double zpps; // zpps=z0/fx + float baseline; // baseline length, for monocular camera,it means the distance of laser to the center of IR-CMOS + double fx; // focus + uint8_t bitSize; // disparity bit size (raw disp bit size, for example: MX6000 is 12, MX6600 is 14) + float unit; // reference units: unit=10 denote 1cm; unit=1 denote 1mm; unit=0.5 denote 0.5mm; and so on + float minDisparity; // dual disparity coefficient + uint8_t packMode; // data pack mode + float dispOffset; // disparity offset, actual disp=chip disp + disp_offset + int32_t invalidDisp; // invalid disparity, usually is 0, dual IR add a auxiliary value. + int32_t dispIntPlace; // disp integer digits, default is 8, Gemini2 XL is 10 + uint8_t isDualCamera; // 0 monocular camera, 1 dual camera +} OBDisparityParam, ob_disparity_param; + +/** + * @brief Enumeration for TOF filter scene ranges + */ +typedef enum { + OB_TOF_FILTER_RANGE_CLOSE = 0, /**< Close range */ + OB_TOF_FILTER_RANGE_MIDDLE = 1, /**< Middle range */ + OB_TOF_FILTER_RANGE_LONG = 2, /**< Long range */ + OB_TOF_FILTER_RANGE_DEBUG = 100, /**< Debug range */ +} OBTofFilterRange, + ob_tof_filter_range, TOF_FILTER_RANGE; +/** + * @brief 3D point structure in the SDK + */ +typedef struct { + float x; ///< X coordinate + float y; ///< Y coordinate + float z; ///< Z coordinate +} OBPoint, ob_point, OBPoint3f, ob_point3f; + +/** + * @brief 2D point structure in the SDK + */ +typedef struct { + float x; ///< X coordinate + float y; ///< Y coordinate +} OBPoint2f, ob_point2f; + +typedef struct { + float *xTable; ///< table used to compute X coordinate + float *yTable; ///< table used to compute Y coordinate + int width; ///< width of x and y tables + int height; ///< height of x and y tables +} OBXYTables, ob_xy_tables; + +/** + * @brief 3D point structure with color information + */ +typedef struct { + float x; ///< X coordinate + float y; ///< Y coordinate + float z; ///< Z coordinate + float r; ///< Red channel component + float g; ///< Green channel component + float b; ///< Blue channel component +} OBColorPoint, ob_color_point; + +/** + * @brief Compression mode + */ +typedef enum { + OB_COMPRESSION_LOSSLESS = 0, /**< Lossless compression mode */ + OB_COMPRESSION_LOSSY = 1, /**< Lossy compression mode */ +} OBCompressionMode, + ob_compression_mode, OB_COMPRESSION_MODE; + +/** + * Compression Params + */ +typedef struct { + /** + * Lossy compression threshold, range [0~255], recommended value is 9, the higher the threshold, the higher the compression ratio. + */ + int threshold; +} OBCompressionParams, ob_compression_params, OB_COMPRESSION_PARAMS; + +/** + * @brief TOF Exposure Threshold + */ +typedef struct { + int32_t upper; ///< Upper threshold, unit: ms + int32_t lower; ///< Lower threshold, unit: ms +} OBTofExposureThresholdControl, ob_tof_exposure_threshold_control, TOF_EXPOSURE_THRESHOLD_CONTROL; + +/** + * @brief Sync mode + * @deprecated This define is deprecated, please use @ref ob_multi_device_sync_mode instead + */ +typedef enum { + /** + * @brief Close synchronize mode + * @brief Single device, neither process input trigger signal nor output trigger signal + * @brief Each Sensor in a single device automatically triggers + */ + OB_SYNC_MODE_CLOSE = 0x00, + + /** + * @brief Standalone synchronize mode + * @brief Single device, neither process input trigger signal nor output trigger signal + * @brief Inside single device, RGB as Major sensor: RGB -> IR/Depth/TOF + */ + OB_SYNC_MODE_STANDALONE = 0x01, + + /** + * @brief Primary synchronize mode + * @brief Primary device. Ignore process input trigger signal, only output trigger signal to secondary devices. + * @brief Inside single device, RGB as Major sensor: RGB -> IR/Depth/TOF + */ + OB_SYNC_MODE_PRIMARY = 0x02, + + /** + * @brief Secondary synchronize mode + * @brief Secondary device. Both process input trigger signal and output trigger signal to other devices. + * @brief Different sensors in a single devices receive trigger signals respectively: ext trigger -> RGB && ext trigger -> IR/Depth/TOF + * + * @attention With the current Gemini 2 device set to this mode, each Sensor receives the first external trigger signal + * after the stream is turned on and starts timing self-triggering at the set frame rate until the stream is turned off + */ + OB_SYNC_MODE_SECONDARY = 0x03, + + /** + * @brief MCU Primary synchronize mode + * @brief Primary device. Ignore process input trigger signal, only output trigger signal to secondary devices. + * @brief Inside device, MCU is the primary signal source: MCU -> RGB && MCU -> IR/Depth/TOF + */ + OB_SYNC_MODE_PRIMARY_MCU_TRIGGER = 0x04, + + /** + * @brief IR Primary synchronize mode + * @brief Primary device. Ignore process input trigger signal, only output trigger signal to secondary devices. + * @brief Inside device, IR is the primary signal source: IR/Depth/TOF -> RGB + */ + OB_SYNC_MODE_PRIMARY_IR_TRIGGER = 0x05, + + /** + * @brief Software trigger synchronize mode + * @brief Host, triggered by software control (receive the upper computer command trigger), at the same time to the trunk output trigger signal + * @brief Different sensors in a single machine receive trigger signals respectively: soft trigger -> RGB && soft trigger -> IR/Depth/TOF + * + * @attention Support product: Gemini2 + */ + OB_SYNC_MODE_PRIMARY_SOFT_TRIGGER = 0x06, + + /** + * @brief Software trigger synchronize mode as secondary device + * @brief The slave receives the external trigger signal (the external trigger signal comes from the soft trigger host) and outputs the trigger signal to + * the external relay. + * @brief Different sensors in a single machine receive trigger signals respectively: ext trigger -> RGB && ext trigger -> IR/Depth/TOF + */ + OB_SYNC_MODE_SECONDARY_SOFT_TRIGGER = 0x07, + + /** + * @brief IR and IMU sync signal + */ + OB_SYNC_MODE_IR_IMU_SYNC = 0x08, + + /** + * @brief Unknown type + */ + OB_SYNC_MODE_UNKNOWN = 0xff, + +} OBSyncMode, + ob_sync_mode, OB_SYNC_MODE; + +/** + * @brief Device synchronization configuration + * @deprecated This structure is deprecated, please use @ref ob_multi_device_sync_config instead + */ +typedef struct { + /** + * @brief Device synchronize mode + */ + OBSyncMode syncMode; + + /** + * @brief IR Trigger signal input delay: Used to configure the delay between the IR/Depth/TOF Sensor receiving the trigger signal and starting exposure, + * Unit: microsecond + * + * @attention This parameter is invalid when the synchronization MODE is set to @ref OB_SYNC_MODE_PRIMARY_IR_TRIGGER + */ + uint16_t irTriggerSignalInDelay; + + /** + * @brief RGB trigger signal input delay is used to configure the delay from the time when an RGB Sensor receives the trigger signal to the time when the + * exposure starts. Unit: microsecond + * + * @attention This parameter is invalid when the synchronization MODE is set to @ref OB_SYNC_MODE_PRIMARY + */ + uint16_t rgbTriggerSignalInDelay; + + /** + * @brief Device trigger signal output delay, used to control the delay configuration of the host device to output trigger signals or the slave device to + * output trigger signals. Unit: microsecond + * + * @attention This parameter is invalid when the synchronization MODE is set to @ref OB_SYNC_MODE_CLOSE or @ref OB_SYNC_MODE_STANDALONE + */ + uint16_t deviceTriggerSignalOutDelay; + + /** + * @brief The device trigger signal output polarity is used to control the polarity configuration of the trigger signal output from the host device or the + * trigger signal output from the slave device + * @brief 0: forward pulse; 1: negative pulse + * + * @attention This parameter is invalid when the synchronization MODE is set to @ref OB_SYNC_MODE_CLOSE or @ref OB_SYNC_MODE_STANDALONE + */ + uint16_t deviceTriggerSignalOutPolarity; + + /** + * @brief MCU trigger frequency, used to configure the output frequency of MCU trigger signal in MCU master mode, unit: Hz + * @brief This configuration will directly affect the image output frame rate of the Sensor. Unit: FPS (frames per second) + * + * @attention This parameter is invalid only when the synchronization MODE is set to @ref OB_SYNC_MODE_PRIMARY_MCU_TRIGGER + */ + uint16_t mcuTriggerFrequency; + + /** + * @brief Device number. Users can mark the device with this number + */ + uint16_t deviceId; + +} OBDeviceSyncConfig, ob_device_sync_config, OB_DEVICE_SYNC_CONFIG; + +/** + * @brief Preset tag + */ +typedef enum { + OB_DEVICE_DEPTH_WORK_MODE = 0, + OB_CUSTOM_DEPTH_WORK_MODE = 1, +} OBDepthWorkModeTag, + ob_depth_work_mode_tag; + +/** + * @brief Depth work mode + */ +typedef struct { + /** + * @brief Checksum of work mode + */ + uint8_t checksum[16]; + + /** + * @brief Name of work mode + */ + char name[32]; + /** + * @brief Preset tag + */ + OBDepthWorkModeTag tag; + +} OBDepthWorkMode, ob_depth_work_mode; + +/** + * @brief SequenceId fliter list item + */ +typedef struct { + int sequenceSelectId; + char name[8]; +} OBSequenceIdItem, ob_sequence_id_item; + +/** + * @brief Hole fillig mode + */ +typedef enum { + OB_HOLE_FILL_TOP = 0, + OB_HOLE_FILL_NEAREST = 1, // "max" means farest for depth, and nearest for disparity; FILL_NEAREST + OB_HOLE_FILL_FAREST = 2, // FILL_FAREST +} OBHoleFillingMode, + ob_hole_filling_mode; + +typedef struct { + uint8_t radius; // window_size +} OBSpatialFastFilterParams, ob_spatial_fast_filter_params; + +typedef struct { + uint8_t radius; // window_size + uint8_t magnitude; // magnitude + uint16_t disp_diff; // smooth_delta +} OBSpatialModerateFilterParams, ob_spatial_moderate_filter_params; + +typedef struct { + uint8_t magnitude; // magnitude + float alpha; // smooth_alpha + uint16_t disp_diff; // smooth_delta + uint16_t radius; // hole_fill +} OBSpatialAdvancedFilterParams, ob_spatial_advanced_filter_params; + +typedef enum OB_EDGE_NOISE_REMOVAL_TYPE { + OB_MG_FILTER = 0, + OB_MGH_FILTER = 1, // horizontal MG + OB_MGA_FILTER = 2, // asym MG + OB_MGC_FILTER = 3, +} OBEdgeNoiseRemovalType, + ob_edge_noise_removal_type; + +typedef struct { + OBEdgeNoiseRemovalType type; + uint16_t marginLeftTh; + uint16_t marginRightTh; + uint16_t marginTopTh; + uint16_t marginBottomTh; +} OBEdgeNoiseRemovalFilterParams, ob_edge_noise_removal_filter_params; + +/** + * @brief Denoising method + */ +typedef enum OB_DDO_NOISE_REMOVAL_TYPE { + OB_NR_LUT = 0, // SPLIT + OB_NR_OVERALL = 1, // NON_SPLIT +} OBDDONoiseRemovalType, + ob_ddo_noise_removal_type; + +typedef struct { + uint16_t max_size; + uint16_t disp_diff; + OBDDONoiseRemovalType type; +} OBNoiseRemovalFilterParams, ob_noise_removal_filter_params; + +/** + * @brief Control command protocol version number + */ +typedef struct { + /** + * @brief Major version number + */ + uint8_t major; + + /** + * @brief Minor version number + */ + uint8_t minor; + + /** + * @brief Patch version number + */ + uint8_t patch; +} OBProtocolVersion, ob_protocol_version; + +/** + * @brief Command version associated with property id + */ +typedef enum { + OB_CMD_VERSION_V0 = (uint16_t)0, ///< Version 1.0 + OB_CMD_VERSION_V1 = (uint16_t)1, ///< Version 2.0 + OB_CMD_VERSION_V2 = (uint16_t)2, ///< Version 3.0 + OB_CMD_VERSION_V3 = (uint16_t)3, ///< Version 4.0 + + OB_CMD_VERSION_NOVERSION = (uint16_t)0xfffe, + OB_CMD_VERSION_INVALID = (uint16_t)0xffff, ///< Invalid version +} OB_CMD_VERSION, + OBCmdVersion, ob_cmd_version; + +/** + * @brief IP address configuration for network devices (IPv4) + */ +typedef struct { + /** + * @brief DHCP status + * + * @note 0: static IP; 1: DHCP + */ + uint16_t dhcp; + + /** + * @brief IP address (IPv4, big endian: 192.168.1.10, address[0] = 192, address[1] = 168, address[2] = 1, address[3] = 10) + */ + uint8_t address[4]; + + /** + * @brief Subnet mask (big endian) + */ + uint8_t mask[4]; + + /** + * @brief Gateway (big endian) + */ + uint8_t gateway[4]; +} OBNetIpConfig, ob_net_ip_config, DEVICE_IP_ADDR_CONFIG; + +#define OBDeviceIpAddrConfig OBNetIpConfig +#define ob_device_ip_addr_config OBNetIpConfig + +/** + * @brief Device communication mode + */ +typedef enum { + OB_COMM_USB = 0x00, ///< USB + OB_COMM_NET = 0x01, ///< Ethernet +} OBCommunicationType, + ob_communication_type, OB_COMMUNICATION_TYPE; + +/** + * @brief USB power status + */ +typedef enum { + OB_USB_POWER_NO_PLUGIN = 0, ///< No plugin + OB_USB_POWER_5V_0A9 = 1, ///< 5V/0.9A + OB_USB_POWER_5V_1A5 = 2, ///< 5V/1.5A + OB_USB_POWER_5V_3A0 = 3, ///< 5V/3.0A +} OBUSBPowerState, + ob_usb_power_state; + +/** + * @brief DC power status + */ +typedef enum { + OB_DC_POWER_NO_PLUGIN = 0, ///< No plugin + OB_DC_POWER_PLUGIN = 1, ///< Plugin +} OBDCPowerState, + ob_dc_power_state; + +/** + * @brief Rotate degree + */ +typedef enum { + OB_ROTATE_DEGREE_0 = 0, ///< Rotate 0 + OB_ROTATE_DEGREE_90 = 90, ///< Rotate 90 + OB_ROTATE_DEGREE_180 = 180, ///< Rotate 180 + OB_ROTATE_DEGREE_270 = 270, ///< Rotate 270 +} ob_rotate_degree_type, + OBRotateDegreeType; + +/** + * @brief Power line frequency mode, for color camera anti-flicker configuration + */ +typedef enum { + OB_POWER_LINE_FREQ_MODE_CLOSE = 0, ///< Close + OB_POWER_LINE_FREQ_MODE_50HZ = 1, ///< 50Hz + OB_POWER_LINE_FREQ_MODE_60HZ = 2, ///< 60Hz +} ob_power_line_freq_mode, + OBPowerLineFreqMode; + +/** + * @brief Frame aggregate output mode + */ +typedef enum { + /** + * @brief Only FrameSet that contains all types of data frames will be output + */ + OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE = 0, + + /** + * @brief Color Frame Require output mode + * @brief Suitable for Color using H264, H265 and other inter-frame encoding format open stream + * + * @attention In this mode, the user may return null when getting a non-Color type data frame from the acquired FrameSet + */ + OB_FRAME_AGGREGATE_OUTPUT_COLOR_FRAME_REQUIRE, + + /** + * @brief FrameSet for any case will be output + * + * @attention In this mode, the user may return null when getting the specified type of data frame from the acquired FrameSet + */ + OB_FRAME_AGGREGATE_OUTPUT_ANY_SITUATION, + /** + * @brief Disable Frame Aggreate + * + * @attention In this mode, All types of data frames will output independently. + */ + OB_FRAME_AGGREGATE_OUTPUT_DISABLE, +} OB_FRAME_AGGREGATE_OUTPUT_MODE, + OBFrameAggregateOutputMode, ob_frame_aggregate_output_mode; +#define OB_FRAME_AGGREGATE_OUTPUT_FULL_FRAME_REQUIRE OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE + +/** + * @brief Enumeration of point cloud coordinate system types + */ +typedef enum { + OB_LEFT_HAND_COORDINATE_SYSTEM = 0, + OB_RIGHT_HAND_COORDINATE_SYSTEM = 1, +} OB_COORDINATE_SYSTEM_TYPE, + OBCoordinateSystemType, ob_coordinate_system_type; + +/** + * @brief Enumeration of device development modes + */ +typedef enum { + /** + * @brief User mode (default mode), which provides full camera device functionality + */ + OB_USER_MODE = 0, + + /** + * @brief Developer mode, which allows developers to access the operating system and software/hardware resources on the device directly + */ + OB_DEVELOPER_MODE = 1, +} OB_DEVICE_DEVELOPMENT_MODE, + OBDeviceDevelopmentMode, ob_device_development_mode; + +/** + * @brief The synchronization mode of the device. + */ +typedef enum { + + /** + * @brief free run mode + * @brief The device does not synchronize with other devices, + * @brief The Color and Depth can be set to different frame rates. + */ + OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN = 1 << 0, + + /** + * @brief standalone mode + * @brief The device does not synchronize with other devices. + * @brief The Color and Depth should be set to same frame rates, the Color and Depth will be synchronized. + */ + OB_MULTI_DEVICE_SYNC_MODE_STANDALONE = 1 << 1, + + /** + * @brief primary mode + * @brief The device is the primary device in the multi-device system, it will output the trigger signal via VSYNC_OUT pin on synchronization port by + * default. + * @brief The Color and Depth should be set to same frame rates, the Color and Depth will be synchronized and can be adjusted by @ref colorDelayUs, @ref + * depthDelayUs or @ref trigger2ImageDelayUs. + */ + OB_MULTI_DEVICE_SYNC_MODE_PRIMARY = 1 << 2, + + /** + * @brief secondary mode + * @brief The device is the secondary device in the multi-device system, it will receive the trigger signal via VSYNC_IN pin on synchronization port. It + * will out the trigger signal via VSYNC_OUT pin on synchronization port by default. + * @brief The Color and Depth should be set to same frame rates, the Color and Depth will be synchronized and can be adjusted by @ref colorDelayUs, @ref + * depthDelayUs or @ref trigger2ImageDelayUs. + * @brief After starting the stream, the device will wait for the trigger signal to start capturing images, and will stop capturing images when the trigger + * signal is stopped. + * + * @attention The frequency of the trigger signal should be same as the frame rate of the stream profile which is set when starting the stream. + */ + OB_MULTI_DEVICE_SYNC_MODE_SECONDARY = 1 << 3, + + /** + * @brief secondary synced mode + * @brief The device is the secondary device in the multi-device system, it will receive the trigger signal via VSYNC_IN pin on synchronization port. It + * will out the trigger signal via VSYNC_OUT pin on synchronization port by default. + * @brief The Color and Depth should be set to same frame rates, the Color and Depth will be synchronized and can be adjusted by @ref colorDelayUs, @ref + * depthDelayUs or @ref trigger2ImageDelayUs. + * @brief After starting the stream, the device will be immediately start capturing images, and will adjust the capture time when the trigger signal is + * received to synchronize with the primary device. If the trigger signal is stopped, the device will still capture images. + * + * @attention The frequency of the trigger signal should be same as the frame rate of the stream profile which is set when starting the stream. + */ + OB_MULTI_DEVICE_SYNC_MODE_SECONDARY_SYNCED = 1 << 4, + + /** + * @brief software triggering mode + * @brief The device will start one time image capture after receiving the capture command and will output the trigger signal via VSYNC_OUT pin by default. + * The capture command can be sent form host by call @ref ob_device_trigger_capture. The number of images captured each time can be set by @ref + * framesPerTrigger. + * @brief The Color and Depth should be set to same frame rates, the Color and Depth will be synchronized and can be adjusted by @ref colorDelayUs, @ref + * depthDelayUs or @ref trigger2ImageDelayUs. + * + * @brief The frequency of the user call @ref ob_device_trigger_capture to send the capture command multiplied by the number of frames per trigger should be + * less than the frame rate of the stream profile which is set when starting the stream. + */ + OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING = 1 << 5, + + /** + * @brief hardware triggering mode + * @brief The device will start one time image capture after receiving the trigger signal via VSYNC_IN pin on synchronization port and will output the + * trigger signal via VSYNC_OUT pin by default. The number of images captured each time can be set by @ref framesPerTrigger. + * @brief The Color and Depth should be set to same frame rates, the Color and Depth will be synchronized and can be adjusted by @ref colorDelayUs, @ref + * depthDelayUs or @ref trigger2ImageDelayUs. + * + * @attention The frequency of the trigger signal multiplied by the number of frames per trigger should be less than the frame rate of the stream profile + * which is set when starting the stream. + * @attention The trigger signal input via VSYNC_IN pin on synchronization port should be ouput by other device via VSYNC_OUT pin in hardware triggering + * mode or software triggering mode. + * @attention Due to different models may have different signal input requirements, please do not use different models to output trigger + * signal as input-trigger signal. + */ + OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING = 1 << 6, + + /** + * @brief IR and IMU sync mode + */ + OB_MULTI_DEVICE_SYNC_MODE_IR_IMU_SYNC = 1 << 7, + +} ob_multi_device_sync_mode, + OBMultiDeviceSyncMode; + +/** + * @brief The synchronization configuration of the device. + */ +typedef struct { + /** + * @brief The sync mode of the device. + */ + OBMultiDeviceSyncMode syncMode; + + /** + * @brief The delay time of the depth image capture after receiving the capture command or trigger signal in microseconds. + * + * @attention This parameter is only valid for some models, please refer to the product manual for details. + */ + int depthDelayUs; + + /** + * @brief The delay time of the color image capture after receiving the capture command or trigger signal in microseconds. + * + * @attention This parameter is only valid for some models, please refer to the product manual for details. + */ + int colorDelayUs; + + /** + * @brief The delay time of the image capture after receiving the capture command or trigger signal in microseconds. + * @brief The depth and color images are captured synchronously as the product design and can not change the delay between the depth and color images. + * + * @attention For Orbbec Astra 2 device, this parameter is valid only when the @ref triggerOutDelayUs is set to 0. + * @attention This parameter is only valid for some models to replace @ref depthDelayUs and @ref colorDelayUs, please refer to the product manual for + * details. + */ + int trigger2ImageDelayUs; + + /** + * @brief Trigger signal output enable flag. + * @brief After the trigger signal output is enabled, the trigger signal will be output when the capture command or trigger signal is received. User can + * adjust the delay time of the trigger signal output by @ref triggerOutDelayUs. + * + * @attention For some models, the trigger signal output is always enabled and cannot be disabled. + * @attention If device is in the @ref OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN or @ref OB_MULTI_DEVICE_SYNC_MODE_STANDALONE mode, the trigger signal output is + * always disabled. Set this parameter to true will not take effect. + */ + bool triggerOutEnable; + + /** + * @brief The delay time of the trigger signal output after receiving the capture command or trigger signal in microseconds. + * + * @attention For Orbbec Astra 2 device, only supported -1 and 0. -1 means the trigger signal output delay is automatically adjusted by the device, 0 means + * the trigger signal output is disabled. + */ + int triggerOutDelayUs; + + /** + * @brief The frame number of each stream after each trigger in triggering mode. + * + * @attention This parameter is only valid when the triggering mode is set to @ref OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING or @ref + * OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING. + * @attention The trigger frequency multiplied by the number of frames per trigger cannot exceed the maximum frame rate of the stream profile which is set + * when starting the stream. + */ + int framesPerTrigger; +} ob_multi_device_sync_config, OBMultiDeviceSyncConfig; + +/** + * @brief The timestamp reset configuration of the device. + * + */ +typedef struct { + /** + * @brief Whether to enable the timestamp reset function. + * @brief If the timestamp reset function is enabled, the timer for calculating the timestamp for output frames will be reset to 0 when the timestamp reset + * command or timestamp reset signal is received, and one timestamp reset signal will be output via TIMER_SYNC_OUT pin on synchronization port by default. + * The timestamp reset signal is input via TIMER_SYNC_IN pin on the synchronization port. + * + * @attention For some models, the timestamp reset function is always enabled and cannot be disabled. + */ + bool enable; + + /** + * @brief The delay time of executing the timestamp reset function after receiving the command or signal in microseconds. + */ + int timestamp_reset_delay_us; + + /** + * @brief the timestamp reset signal output enable flag. + * + * @attention For some models, the timestamp reset signal output is always enabled and cannot be disabled. + */ + bool timestamp_reset_signal_output_enable; +} ob_device_timestamp_reset_config, OBDeviceTimestampResetConfig; + +/** + * @brief Baseline calibration parameters + */ +typedef struct { + /** + * @brief Baseline length + */ + float baseline; + /** + * @brief Calibration distance + */ + float zpd; +} BASELINE_CALIBRATION_PARAM, ob_baseline_calibration_param, OBBaselineCalibrationParam; + +/** + * @brief HDR Configuration + */ +typedef struct { + + /** + * @brief Enable/disable HDR, after enabling HDR, the exposure_1 and gain_1 will be used as the first exposure and gain, and the exposure_2 and gain_2 will + * be used as the second exposure and gain. The output image will be alternately exposed and gain between the first and second + * exposure and gain. + * + * @attention After enabling HDR, the auto exposure will be disabled. + */ + uint8_t enable; + uint8_t sequence_name; ///< Sequence name + uint32_t exposure_1; ///< Exposure time 1 + uint32_t gain_1; ///< Gain 1 + uint32_t exposure_2; ///< Exposure time 2 + uint32_t gain_2; ///< Gain 2 +} HDR_CONFIG, ob_hdr_config, OBHdrConfig; + +/** + * @brief The rect of the region of interest + */ +typedef struct { + int16_t x0_left; + int16_t y0_top; + int16_t x1_right; + int16_t y1_bottom; +} AE_ROI, ob_region_of_interest, OBRegionOfInterest; + +typedef enum { + OB_FILTER_CONFIG_VALUE_TYPE_INVALID = -1, + OB_FILTER_CONFIG_VALUE_TYPE_INT = 0, + OB_FILTER_CONFIG_VALUE_TYPE_FLOAT = 1, + OB_FILTER_CONFIG_VALUE_TYPE_BOOLEAN = 2, +} OBFilterConfigValueType, + ob_filter_config_value_type; +/** + * @brief Configuration Item for the filter + */ +typedef struct { + const char *name; ///< Name of the configuration item + OBFilterConfigValueType type; ///< Value type of the configuration item + double min; ///< Minimum value casted to double + double max; ///< Maximum value casted to double + double step; ///< Step value casted to double + double def; ///< Default value casted to double + const char *desc; ///< Description of the configuration item +} OBFilterConfigSchemaItem, ob_filter_config_schema_item; + +/** + * @brief struct of serial number + */ +typedef struct { + char numberStr[16]; +} OBDeviceSerialNumber, ob_device_serial_number, OBSerialNumber, ob_serial_number; + +/** + * @brief Disparity offset interleaving configuration + */ +typedef struct { + uint8_t enable; + uint8_t offset0; + uint8_t offset1; + uint8_t reserved; +} OBDispOffsetConfig, ob_disp_offset_config; + +/** + * @brief Frame metadata types + * @brief The frame metadata is a set of meta info generated by the device for current individual frame. + */ +typedef enum { + /** + * @brief Timestamp when the frame is captured. + * @attention Different device models may have different units. It is recommended to use the timestamp related functions to get the timestamp in the + * correct units. + */ + OB_FRAME_METADATA_TYPE_TIMESTAMP = 0, + + /** + * @brief Timestamp in the middle of the capture. + * @brief Usually is the middle of the exposure time. + * + * @attention Different device models may have different units. + */ + OB_FRAME_METADATA_TYPE_SENSOR_TIMESTAMP = 1, + + /** + * @brief The number of current frame. + */ + OB_FRAME_METADATA_TYPE_FRAME_NUMBER = 2, + + /** + * @brief Auto exposure status + * @brief If the value is 0, it means the auto exposure is disabled. Otherwise, it means the auto exposure is enabled. + */ + OB_FRAME_METADATA_TYPE_AUTO_EXPOSURE = 3, + + /** + * @brief Exposure time + * + * @attention Different sensor may have different units. Usually, it is 100us for color sensor and 1us for depth/infrared sensor. + */ + OB_FRAME_METADATA_TYPE_EXPOSURE = 4, + + /** + * @brief Gain + * + * @attention For some device models, the gain value represents the gain level, not the multiplier. + */ + OB_FRAME_METADATA_TYPE_GAIN = 5, + + /** + * @brief Auto white balance status + * @brief If the value is 0, it means the auto white balance is disabled. Otherwise, it means the auto white balance is enabled. + */ + OB_FRAME_METADATA_TYPE_AUTO_WHITE_BALANCE = 6, + + /** + * @brief White balance + */ + OB_FRAME_METADATA_TYPE_WHITE_BALANCE = 7, + + /** + * @brief Brightness + */ + OB_FRAME_METADATA_TYPE_BRIGHTNESS = 8, + + /** + * @brief Contrast + */ + OB_FRAME_METADATA_TYPE_CONTRAST = 9, + + /** + * @brief Saturation + */ + OB_FRAME_METADATA_TYPE_SATURATION = 10, + + /** + * @brief Sharpness + */ + OB_FRAME_METADATA_TYPE_SHARPNESS = 11, + + /** + * @brief Backlight compensation + */ + OB_FRAME_METADATA_TYPE_BACKLIGHT_COMPENSATION = 12, + + /** + * @brief Hue + */ + OB_FRAME_METADATA_TYPE_HUE = 13, + + /** + * @brief Gamma + */ + OB_FRAME_METADATA_TYPE_GAMMA = 14, + + /** + * @brief Power line frequency + * @brief For anti-flickering, 0: Close, 1: 50Hz, 2: 60Hz, 3: Auto + */ + OB_FRAME_METADATA_TYPE_POWER_LINE_FREQUENCY = 15, + + /** + * @brief Low light compensation + * + * @attention The low light compensation is a feature inside the device, and can not manually control it. + */ + OB_FRAME_METADATA_TYPE_LOW_LIGHT_COMPENSATION = 16, + + /** + * @brief Manual white balance setting + */ + OB_FRAME_METADATA_TYPE_MANUAL_WHITE_BALANCE = 17, + + /** + * @brief Actual frame rate + * @brief The actual frame rate will be calculated according to the exposure time and other parameters. + */ + OB_FRAME_METADATA_TYPE_ACTUAL_FRAME_RATE = 18, + + /** + * @brief Frame rate + */ + OB_FRAME_METADATA_TYPE_FRAME_RATE = 19, + + /** + * @brief Left region of interest for the auto exposure Algorithm. + */ + OB_FRAME_METADATA_TYPE_AE_ROI_LEFT = 20, + + /** + * @brief Top region of interest for the auto exposure Algorithm. + */ + OB_FRAME_METADATA_TYPE_AE_ROI_TOP = 21, + + /** + * @brief Right region of interest for the auto exposure Algorithm. + */ + OB_FRAME_METADATA_TYPE_AE_ROI_RIGHT = 22, + + /** + * @brief Bottom region of interest for the auto exposure Algorithm. + */ + OB_FRAME_METADATA_TYPE_AE_ROI_BOTTOM = 23, + + /** + * @brief Exposure priority + */ + OB_FRAME_METADATA_TYPE_EXPOSURE_PRIORITY = 24, + + /** + * @brief HDR sequence name + */ + OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_NAME = 25, + + /** + * @brief HDR sequence size + */ + OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_SIZE = 26, + + /** + * @brief HDR sequence index + */ + OB_FRAME_METADATA_TYPE_HDR_SEQUENCE_INDEX = 27, + + /** + * @brief Laser power value in mW + * + * @attention The laser power value is an approximate estimation. + */ + OB_FRAME_METADATA_TYPE_LASER_POWER = 28, + + /** + * @brief Laser power level + */ + OB_FRAME_METADATA_TYPE_LASER_POWER_LEVEL = 29, + + /** + * @brief Laser status + * @brief 0: Laser off, 1: Laser on + */ + OB_FRAME_METADATA_TYPE_LASER_STATUS = 30, + + /** + * @brief GPIO input data + */ + OB_FRAME_METADATA_TYPE_GPIO_INPUT_DATA = 31, + + /** + * @brief disparity search offset value + */ + OB_FRAME_METADATA_TYPE_DISPARITY_SEARCH_OFFSET = 32, + + /** + * @brief disparity search range + */ + OB_FRAME_METADATA_TYPE_DISPARITY_SEARCH_RANGE = 33, + + /** + * @brief The number of frame metadata types, using for types iterating + * @attention It is not a valid frame metadata type + */ + OB_FRAME_METADATA_TYPE_COUNT, +} ob_frame_metadata_type, + OBFrameMetadataType; + +/** + * @brief For Linux, there are two ways to access the UVC device, libuvc and v4l2. The backend type is used to select the backend to access the device. + * + */ +typedef enum { + /** + * @brief Auto detect system capabilities and device hint to select backend + * + */ + OB_UVC_BACKEND_TYPE_AUTO, + + /** + * @brief Use libuvc backend to access the UVC device + * + */ + OB_UVC_BACKEND_TYPE_LIBUVC, + + /** + * @brief Use v4l2 backend to access the UVC device + * + */ + OB_UVC_BACKEND_TYPE_V4L2, + + /** + * @brief Use MSMF backend to access the UVC device + */ + OB_UVC_BACKEND_TYPE_MSMF, +} ob_uvc_backend_type, + OBUvcBackendType; + +/** + * @brief The playback status of the media + */ +typedef enum { + OB_PLAYBACK_UNKNOWN, + OB_PLAYBACK_PLAYING, /**< The media is playing */ + OB_PLAYBACK_PAUSED, /**< The media is paused */ + OB_PLAYBACK_STOPPED, /**< The media is stopped */ + OB_PLAYBACK_COUNT, +} ob_playback_status, + OBPlaybackStatus; + +/** + * @brief Intra-camera Sync Reference based on the exposure start time, the exposure middle time, or the exposure end time. + */ +typedef enum { + START_OF_EXPOSURE = 0, /**< start of exposure */ + MIDDLE_OF_EXPOSURE, /**< middle of exposure */ + END_OF_EXPOSURE, /**< end of exposure */ +} ob_intra_camera_sync_reference, + OBIntraCameraSyncReference; + +// For compatibility +#define OB_FRAME_METADATA_TYPE_LASER_POWER_MODE OB_FRAME_METADATA_TYPE_LASER_POWER_LEVEL +#define OB_FRAME_METADATA_TYPE_EMITTER_MODE OB_FRAME_METADATA_TYPE_LASER_STATUS + +/** + * @brief Callback for file transfer + * + * @param state Transmission status + * @param message Transfer status information + * @param percent Transfer progress percentage + * @param user_data User-defined data + */ +typedef void (*ob_file_send_callback)(ob_file_tran_state state, const char *message, uint8_t percent, void *user_data); + +/** + * @brief Callback for firmware upgrade + * + * @param state Upgrade status + * @param message Upgrade status information + * @param percent Upgrade progress percentage + * @param user_data User-defined data + */ +typedef void (*ob_device_fw_update_callback)(ob_fw_update_state state, const char *message, uint8_t percent, void *user_data); + +/** + * @brief Callback for device status + * + * @param state Device status + * @param message Device status information + * @param user_data User-defined data + */ +typedef void (*ob_device_state_callback)(ob_device_state state, const char *message, void *user_data); + +/** + * @brief Callback for writing data + * + * @param state Write data status + * @param percent Write data percentage + * @param user_data User-defined data + */ +typedef void (*ob_set_data_callback)(ob_data_tran_state state, uint8_t percent, void *user_data); + +/** + * @brief Callback for reading data + * + * @param state Read data status + * @param dataChunk Read the returned data block + * @param user_data User-defined data + */ +typedef void (*ob_get_data_callback)(ob_data_tran_state state, ob_data_chunk *dataChunk, void *user_data); + +/** + * @brief Callback for media status (recording and playback) + * + * @param state Condition + * @param user_data User-defined data + */ +typedef void (*ob_media_state_callback)(ob_media_state state, void *user_data); + +/** + * @brief Callback for device change + * + * @param removed List of deleted (dropped) devices + * @param added List of added (online) devices + * @param user_data User-defined data + */ +typedef void (*ob_device_changed_callback)(ob_device_list *removed, ob_device_list *added, void *user_data); + +// typedef void (*ob_net_device_added_callback)(const char *added, void *user_data); +// typedef void (*ob_net_device_removed_callback)(const char *removed, void *user_data); + +/** + * @brief Callback for frame + * + * @param frame Frame object + * @param user_data User-defined data + */ +typedef void (*ob_frame_callback)(ob_frame *frame, void *user_data); +#define ob_filter_callback ob_frame_callback +#define ob_playback_callback ob_frame_callback + +/** + * @brief Callback for frameset + * + * @param frameset Frameset object + * @param user_data User-defined data + */ +typedef void (*ob_frameset_callback)(ob_frame *frameset, void *user_data); + +/** + * @brief Customize the delete callback + * + * @param buffer Data that needs to be deleted + * @param user_data User-defined data + */ +typedef void(ob_frame_destroy_callback)(uint8_t *buffer, void *user_data); + +/** + * @brief Callback for receiving log + * + * @param severity Current log level + * @param message Log message + * @param user_data User-defined data + */ +typedef void(ob_log_callback)(ob_log_severity severity, const char *message, void *user_data); + +typedef void (*ob_playback_status_changed_callback)(ob_playback_status status, void *user_data); +/** + * @brief Check if the sensor_type is a video sensor + * + * @param sensor_type Sensor type to check + * @return True if sensor_type is a video sensor, false otherwise + */ +#define ob_is_video_sensor_type(sensor_type) \ + (sensor_type == OB_SENSOR_COLOR || sensor_type == OB_SENSOR_DEPTH || sensor_type == OB_SENSOR_IR || sensor_type == OB_SENSOR_IR_LEFT \ + || sensor_type == OB_SENSOR_IR_RIGHT || sensor_type == OB_SENSOR_CONFIDENCE) + +/** + * @brief check if the stream_type is a video stream + * + * @param stream_type Stream type to check + * @return True if stream_type is a video stream, false otherwise + */ +#define ob_is_video_stream_type(stream_type) \ + (stream_type == OB_STREAM_COLOR || stream_type == OB_STREAM_DEPTH || stream_type == OB_STREAM_IR || stream_type == OB_STREAM_IR_LEFT \ + || stream_type == OB_STREAM_IR_RIGHT || stream_type == OB_STREAM_VIDEO || stream_type == OB_STREAM_CONFIDENCE) + +/** + * @brief Check if sensor_type is an IR sensor + * + * @param sensor_type Sensor type to check + * @return True if sensor_type is an IR sensor, false otherwise + */ +#define is_ir_sensor(sensor_type) (sensor_type == OB_SENSOR_IR || sensor_type == OB_SENSOR_IR_LEFT || sensor_type == OB_SENSOR_IR_RIGHT) +#define isIRSensor is_ir_sensor + +/** + * @brief Check if stream_type is an IR stream + * + * @param stream_type Stream type to check + * @return True if stream_type is an IR stream, false otherwise + */ +#define is_ir_stream(stream_type) (stream_type == OB_STREAM_IR || stream_type == OB_STREAM_IR_LEFT || stream_type == OB_STREAM_IR_RIGHT) +#define isIRStream is_ir_stream + +/** + * @brief Check if frame_type is an IR frame + * + * @param frame_type Frame type to check + * @return True if frame_type is an IR frame, false otherwise + */ +#define is_ir_frame(frame_type) (frame_type == OB_FRAME_IR || frame_type == OB_FRAME_IR_LEFT || frame_type == OB_FRAME_IR_RIGHT) +#define isIRFrame is_ir_frame + +/** + * @brief The default Decrypt Key + */ +#define OB_DEFAULT_DECRYPT_KEY (nullptr) + +#ifdef __cplusplus +} +#endif + +#pragma pack(pop) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Pipeline.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Pipeline.h new file mode 100644 index 0000000..c670748 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Pipeline.h @@ -0,0 +1,335 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Pipeline.h + * @brief The SDK's advanced API can quickly implement functions such as switching streaming, frame synchronization, software filtering, etc., suitable for + * applications, and the algorithm focuses on rgbd data stream scenarios. If you are on real-time or need to handle synchronization separately, align the scene. + * Please use the interface of Device's Lower API. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Create a pipeline object + * + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_pipeline* return the pipeline object + */ +OB_EXPORT ob_pipeline *ob_create_pipeline(ob_error **error); + +/** + * @brief Using device objects to create pipeline objects + * + * @param[in] dev Device object used to create pipeline + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_pipeline* return the pipeline object + */ +OB_EXPORT ob_pipeline *ob_create_pipeline_with_device(const ob_device *dev, ob_error **error); + +/** + * @brief Delete pipeline objects + * + * @param[in] pipeline The pipeline object to be deleted + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_pipeline(ob_pipeline *pipeline, ob_error **error); + +/** + * @brief Start the pipeline with default parameters + * + * @param[in] pipeline pipeline object + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_pipeline_start(ob_pipeline *pipeline, ob_error **error); + +/** + * @brief Start the pipeline with configuration parameters + * + * @param[in] pipeline pipeline object + * @param[in] config Parameters to be configured + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_pipeline_start_with_config(ob_pipeline *pipeline, const ob_config *config, ob_error **error); + +/** + * @brief Start the pipeline and set the frame collection data callback + * + * @attention After start the pipeline with this interface, the frames will be output to the callback function and cannot be obtained frames by call + * @ob_pipeline_wait_for_frameset + * + * @param[in] pipeline pipeline object + * @param[in] config Parameters to be configured + * @param[in] callback Trigger a callback when all frame data in the frameset arrives + * @param[in] user_data Pass in any user data and get it from the callback + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_pipeline_start_with_callback(ob_pipeline *pipeline, const ob_config *config, ob_frameset_callback callback, void *user_data, + ob_error **error); + +/** + * @brief Stop pipeline + * + * @param[in] pipeline pipeline object + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_pipeline_stop(ob_pipeline *pipeline, ob_error **error); + +/** + * @brief Get the configuration object associated with the pipeline + * @brief Returns default configuration if the user has not configured + * + * @param[in] pipeline The pipeline object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_config* The configuration object + */ +OB_EXPORT ob_config *ob_pipeline_get_config(const ob_pipeline *pipeline, ob_error **error); + +/** + * @brief Switch the corresponding configuration + * + * @param[in] pipeline The pipeline object + * @param[in] config The pipeline configuration + * @param[out] error Log error messages + */ +OB_EXPORT void ob_pipeline_switch_config(ob_pipeline *pipeline, ob_config *config, ob_error **error); + +/** + * @brief Wait for a set of frames to be returned synchronously + * + * @param[in] pipeline The pipeline object + * @param[in] timeout_ms The timeout for waiting (in milliseconds) + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_frame* The frameset that was waited for. A frameset is a special frame that can be used to obtain independent frames from the set. + */ +OB_EXPORT ob_frame *ob_pipeline_wait_for_frameset(ob_pipeline *pipeline, uint32_t timeout_ms, ob_error **error); + +/** + * @brief Get the device object associated with the pipeline + * + * @param[in] pipeline The pipeline object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_device* The device object + */ +OB_EXPORT ob_device *ob_pipeline_get_device(const ob_pipeline *pipeline, ob_error **error); + +/** + * @brief Get the stream profile list associated with the pipeline + * + * @param[in] pipeline The pipeline object + * @param[in] sensorType The sensor type. The supported sensor types can be obtained through the ob_device_get_sensor_list() interface. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_stream_profile_list* The stream profile list + */ +OB_EXPORT ob_stream_profile_list *ob_pipeline_get_stream_profile_list(const ob_pipeline *pipeline, ob_sensor_type sensorType, ob_error **error); + +/** + * @brief Enable frame synchronization + * @brief Synchronize the frames of different streams by using the timestamp information of the frames. + * @brief Dynamically (when pipeline is started) enable/disable frame synchronization is allowed. + * + * @param[in] pipeline The pipeline object + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_pipeline_enable_frame_sync(ob_pipeline *pipeline, ob_error **error); + +/** + * @brief Disable frame synchronization + * + * @param[in] pipeline The pipeline object + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_pipeline_disable_frame_sync(ob_pipeline *pipeline, ob_error **error); + +/** + * @brief Return a list of D2C-enabled depth sensor resolutions corresponding to the input color sensor resolution + * + * @param[in] pipeline The pipeline object + * @param[in] color_profile The input profile of the color sensor + * @param[in] align_mode The input align mode + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_stream_profile_list* The list of D2C-enabled depth sensor resolutions + */ +OB_EXPORT ob_stream_profile_list *ob_get_d2c_depth_profile_list(const ob_pipeline *pipeline, const ob_stream_profile *color_profile, ob_align_mode align_mode, + ob_error **error); + +/** + * @brief Create the pipeline configuration + * + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_config* The configuration object + */ +OB_EXPORT ob_config *ob_create_config(ob_error **error); + +/** + * @brief Delete the pipeline configuration + * + * @param[in] config The configuration to be deleted + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_config(ob_config *config, ob_error **error); + +/** + * @brief Enable a stream with default profile + * + * @param[in] config The pipeline configuration object + * @param[in] stream_type The type of the stream to be enabled + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_enable_stream(ob_config *config, ob_stream_type stream_type, ob_error **error); + +/** + * @brief Enable all streams in the pipeline configuration + * + * @param[in] config The pipeline configuration + * @param[out] error Log error messages + */ +OB_EXPORT void ob_config_enable_all_stream(ob_config *config, ob_error **error); + +/** + * @brief Enable a stream according to the stream profile + * + * @param[in] config The pipeline configuration object + * @param[in] profile The stream profile to be enabled + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_enable_stream_with_stream_profile(ob_config *config, const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Enable video stream with specified parameters + * + * @attention The stream_type should be a video stream type, such as OB_STREAM_IR, OB_STREAM_COLOR, OB_STREAM_DEPTH, etc. + * + * @param[in] config The pipeline configuration object + * @param[in] stream_type The type of the stream to be enabled + * @param[in] width The width of the video stream + * @param[in] height The height of the video stream + * @param[in] fps The frame rate of the video stream + * @param[in] format The format of the video stream + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_enable_video_stream(ob_config *config, ob_stream_type stream_type, uint32_t width, uint32_t height, uint32_t fps, ob_format format, + ob_error **error); + +/** + * @brief Enable accelerometer stream with specified parameters + * + * @param[in] config The pipeline configuration object + * @param[in] full_scale_range The full scale range of the accelerometer + * @param[in] sample_rate The sample rate of the accelerometer + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_enable_accel_stream(ob_config *config, ob_accel_full_scale_range full_scale_range, ob_accel_sample_rate sample_rate, ob_error **error); + +/** + * @brief Enable gyroscope stream with specified parameters + * + * @param[in] config The pipeline configuration object + * @param[in] full_scale_range The full scale range of the gyroscope + * @param[in] sample_rate The sample rate of the gyroscope + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_enable_gyro_stream(ob_config *config, ob_gyro_full_scale_range full_scale_range, ob_gyro_sample_rate sample_rate, ob_error **error); + +/** + * @brief Get the enabled stream profile list in the pipeline configuration + * + * @param config The pipeline configuration object + * @param error Pointer to an error object that will be set if an error occurs. + * @return ob_stream_profile_list* The enabled stream profile list, should be released by @ref ob_delete_stream_profile_list after use + */ +OB_EXPORT ob_stream_profile_list *ob_config_get_enabled_stream_profile_list(const ob_config *config, ob_error **error); + +/** + * @brief Disable a specific stream in the pipeline configuration + * + * @param[in] config The pipeline configuration object + * @param[in] type The type of stream to be disabled + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_disable_stream(ob_config *config, ob_stream_type type, ob_error **error); + +/** + * @brief Disable all streams in the pipeline configuration + * + * @param[in] config The pipeline configuration object + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_disable_all_stream(ob_config *config, ob_error **error); + +/** + * @brief Set the alignment mode for the pipeline configuration + * + * @param[in] config The pipeline configuration object + * @param[in] mode The alignment mode to be set + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_set_align_mode(ob_config *config, ob_align_mode mode, ob_error **error); + +/** + * @brief Set whether depth scaling is required after enable depth to color alignment + * @brief After enabling depth to color alignment, the depth image may need to be scaled to match the color image size. + * + * @param[in] config The pipeline configuration object + * @param[in] enable Whether scaling is required + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_set_depth_scale_after_align_require(ob_config *config, bool enable, ob_error **error); + +/** + * @brief Set the frame aggregation output mode for the pipeline configuration + * @brief The processing strategy when the FrameSet generated by the frame aggregation function does not contain the frames of all opened streams (which + * can be caused by different frame rates of each stream, or by the loss of frames of one stream): drop directly or output to the user. + * + * @param[in] config The pipeline configuration object + * @param[in] mode The frame aggregation output mode to be set (default mode is @ref OB_FRAME_AGGREGATE_OUTPUT_ANY_SITUATION) + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_config_set_frame_aggregate_output_mode(ob_config *config, ob_frame_aggregate_output_mode mode, ob_error **error); + +/** + * @brief Get current camera parameters + * @attention If D2C is enabled, it will return the camera parameters after D2C, if not, it will return to the default parameters + * + * @param[in] pipeline pipeline object + * @param[out] error Log error messages + * @return ob_camera_param The camera internal parameters + */ +OB_EXPORT ob_camera_param ob_pipeline_get_camera_param(ob_pipeline *pipeline, ob_error **error); + +/** + * @brief Get the current camera parameters + * + * @param[in] pipeline pipeline object + * @param[in] colorWidth color width + * @param[in] colorHeight color height + * @param[in] depthWidth depth width + * @param[in] depthHeight depth height + * @param[out] error Log error messages + * @return ob_camera_param returns camera internal parameters + */ +OB_EXPORT ob_camera_param ob_pipeline_get_camera_param_with_profile(ob_pipeline *pipeline, uint32_t colorWidth, uint32_t colorHeight, uint32_t depthWidth, + uint32_t depthHeight, ob_error **error); + +/** + * @brief Get device calibration parameters with the specified configuration + * + * @param[in] pipeline pipeline object + * @param[in] config The pipeline configuration + * @param[out] error Log error messages + * @return ob_calibration_param The calibration parameters + */ +OB_EXPORT ob_calibration_param ob_pipeline_get_calibration_param(ob_pipeline *pipeline, ob_config *config, ob_error **error); + +// The following interfaces are deprecated and are retained here for compatibility purposes. +#define ob_config_set_depth_scale_require ob_config_set_depth_scale_after_align_require + +#ifdef __cplusplus +} +#endif + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Property.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Property.h new file mode 100644 index 0000000..7600968 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Property.h @@ -0,0 +1,919 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Property.h + * @brief Control command property list maintenance + */ + +#pragma once + +#include "ObTypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumeration value describing all attribute control commands of the device + */ +typedef enum { + /** + * @brief LDP switch + */ + OB_PROP_LDP_BOOL = 2, + + /** + * @brief Laser switch + */ + OB_PROP_LASER_BOOL = 3, + + /** + * @brief laser pulse width + */ + OB_PROP_LASER_PULSE_WIDTH_INT = 4, + + /** + * @brief Laser current (uint: mA) + */ + OB_PROP_LASER_CURRENT_FLOAT = 5, + + /** + * @brief IR flood switch + */ + OB_PROP_FLOOD_BOOL = 6, + + /** + * @brief IR flood level + */ + OB_PROP_FLOOD_LEVEL_INT = 7, + + /** + * @brief Enable/disable temperature compensation + * + */ + OB_PROP_TEMPERATURE_COMPENSATION_BOOL = 8, + + /** + * @brief Depth mirror + */ + OB_PROP_DEPTH_MIRROR_BOOL = 14, + + /** + * @brief Depth flip + */ + OB_PROP_DEPTH_FLIP_BOOL = 15, + + /** + * @brief Depth Postfilter + */ + OB_PROP_DEPTH_POSTFILTER_BOOL = 16, + + /** + * @brief Depth Holefilter + */ + OB_PROP_DEPTH_HOLEFILTER_BOOL = 17, + + /** + * @brief IR mirror + */ + OB_PROP_IR_MIRROR_BOOL = 18, + + /** + * @brief IR flip + */ + OB_PROP_IR_FLIP_BOOL = 19, + + /** + * @brief Minimum depth threshold + */ + OB_PROP_MIN_DEPTH_INT = 22, + + /** + * @brief Maximum depth threshold + */ + OB_PROP_MAX_DEPTH_INT = 23, + + /** + * @brief Software filter switch + */ + OB_PROP_DEPTH_NOISE_REMOVAL_FILTER_BOOL = 24, + + /** + * @brief LDP status + */ + OB_PROP_LDP_STATUS_BOOL = 32, + + /** + * @brief maxdiff for depth noise removal filter + */ + OB_PROP_DEPTH_NOISE_REMOVAL_FILTER_MAX_DIFF_INT = 40, + + /** + * @brief maxSpeckleSize for depth noise removal filter + */ + OB_PROP_DEPTH_NOISE_REMOVAL_FILTER_MAX_SPECKLE_SIZE_INT = 41, + + /** + * @brief Hardware d2c is on + */ + OB_PROP_DEPTH_ALIGN_HARDWARE_BOOL = 42, + + /** + * @brief Timestamp adjustment + */ + OB_PROP_TIMESTAMP_OFFSET_INT = 43, + + /** + * @brief Hardware distortion switch Rectify + */ + OB_PROP_HARDWARE_DISTORTION_SWITCH_BOOL = 61, + + /** + * @brief Fan mode switch + */ + OB_PROP_FAN_WORK_MODE_INT = 62, + + /** + * @brief Multi-resolution D2C mode + */ + OB_PROP_DEPTH_ALIGN_HARDWARE_MODE_INT = 63, + + /** + * @brief Anti_collusion activation status + */ + OB_PROP_ANTI_COLLUSION_ACTIVATION_STATUS_BOOL = 64, + + /** + * @brief the depth precision level, which may change the depth frame data unit, needs to be confirmed through the ValueScale interface of + * DepthFrame + */ + OB_PROP_DEPTH_PRECISION_LEVEL_INT = 75, + + /** + * @brief tof filter range configuration + */ + OB_PROP_TOF_FILTER_RANGE_INT = 76, + + /** + * @brief laser mode, the firmware terminal currently only return 1: IR Drive, 2: Torch + */ + OB_PROP_LASER_MODE_INT = 79, + + /** + * @brief brt2r-rectify function switch (brt2r is a special module on mx6600), 0: Disable, 1: Rectify Enable + */ + OB_PROP_RECTIFY2_BOOL = 80, + + /** + * @brief Color mirror + */ + OB_PROP_COLOR_MIRROR_BOOL = 81, + + /** + * @brief Color flip + */ + OB_PROP_COLOR_FLIP_BOOL = 82, + + /** + * @brief Indicator switch, 0: Disable, 1: Enable + */ + OB_PROP_INDICATOR_LIGHT_BOOL = 83, + + /** + * @brief Disparity to depth switch, false: switch to software disparity convert to depth, true: switch to hardware disparity convert to depth + */ + OB_PROP_DISPARITY_TO_DEPTH_BOOL = 85, + + /** + * @brief BRT function switch (anti-background interference), 0: Disable, 1: Enable + */ + OB_PROP_BRT_BOOL = 86, + + /** + * @brief Watchdog function switch, 0: Disable, 1: Enable + */ + OB_PROP_WATCHDOG_BOOL = 87, + + /** + * @brief External signal trigger restart function switch, 0: Disable, 1: Enable + */ + OB_PROP_EXTERNAL_SIGNAL_RESET_BOOL = 88, + + /** + * @brief Heartbeat monitoring function switch, 0: Disable, 1: Enable + */ + OB_PROP_HEARTBEAT_BOOL = 89, + + /** + * @brief Depth cropping mode device: OB_DEPTH_CROPPING_MODE + */ + OB_PROP_DEPTH_CROPPING_MODE_INT = 90, + + /** + * @brief D2C preprocessing switch (such as RGB cropping), 0: off, 1: on + */ + OB_PROP_D2C_PREPROCESS_BOOL = 91, + + /** + * @brief Enable/disable GPM function + */ + OB_PROP_GPM_BOOL = 93, + + /** + * @brief Custom RGB cropping switch, 0 is off, 1 is on custom cropping, and the ROI cropping area is issued + */ + OB_PROP_RGB_CUSTOM_CROP_BOOL = 94, + + /** + * @brief Device operating mode (power consumption) + */ + OB_PROP_DEVICE_WORK_MODE_INT = 95, + + /** + * @brief Device communication type, 0: USB; 1: Ethernet(RTSP) + */ + OB_PROP_DEVICE_COMMUNICATION_TYPE_INT = 97, + + /** + * @brief Switch infrared imaging mode, 0: active IR mode, 1: passive IR mode + */ + OB_PROP_SWITCH_IR_MODE_INT = 98, + + /** + * @brief Laser power level + */ + OB_PROP_LASER_POWER_LEVEL_CONTROL_INT = 99, + + /** + * @brief LDP's measure distance, unit: mm + */ + OB_PROP_LDP_MEASURE_DISTANCE_INT = 100, + + /** + * @brief Reset device time to zero + */ + OB_PROP_TIMER_RESET_SIGNAL_BOOL = 104, + + /** + * @brief Enable send reset device time signal to other device. true: enable, false: disable + */ + OB_PROP_TIMER_RESET_TRIGGER_OUT_ENABLE_BOOL = 105, + + /** + * @brief Delay to reset device time, unit: us + */ + OB_PROP_TIMER_RESET_DELAY_US_INT = 106, + + /** + * @brief Signal to capture image + */ + OB_PROP_CAPTURE_IMAGE_SIGNAL_BOOL = 107, + + /** + * @brief Right IR sensor mirror state + */ + OB_PROP_IR_RIGHT_MIRROR_BOOL = 112, + + /** + * @brief Number frame to capture once a 'OB_PROP_CAPTURE_IMAGE_SIGNAL_BOOL' effect. range: [1, 255] + */ + OB_PROP_CAPTURE_IMAGE_FRAME_NUMBER_INT = 113, + + /** + * @brief Right IR sensor flip state. true: flip image, false: origin, default: false + */ + OB_PROP_IR_RIGHT_FLIP_BOOL = 114, + + /** + * @brief Color sensor rotation, angle{0, 90, 180, 270} + */ + OB_PROP_COLOR_ROTATE_INT = 115, + + /** + * @brief IR/Left-IR sensor rotation, angle{0, 90, 180, 270} + */ + OB_PROP_IR_ROTATE_INT = 116, + + /** + * @brief Right IR sensor rotation, angle{0, 90, 180, 270} + */ + OB_PROP_IR_RIGHT_ROTATE_INT = 117, + + /** + * @brief Depth sensor rotation, angle{0, 90, 180, 270} + */ + OB_PROP_DEPTH_ROTATE_INT = 118, + + /** + * @brief Get hardware laser power actual level which real state of laser element. OB_PROP_LASER_POWER_LEVEL_CONTROL_INT99 will effect this command + * which it setting and changed the hardware laser energy level. + */ + OB_PROP_LASER_POWER_ACTUAL_LEVEL_INT = 119, + + /** + * @brief USB's power state, enum type: OBUSBPowerState + */ + OB_PROP_USB_POWER_STATE_INT = 121, + + /** + * @brief DC's power state, enum type: OBDCPowerState + */ + OB_PROP_DC_POWER_STATE_INT = 122, + + /** + * @brief Device development mode switch, optional modes can refer to the definition in @ref OBDeviceDevelopmentMode,the default mode is + * @ref OB_USER_MODE + * @attention The device takes effect after rebooting when switching modes. + */ + OB_PROP_DEVICE_DEVELOPMENT_MODE_INT = 129, + + /** + * @brief Multi-DeviceSync synchronized signal trigger out is enable state. true: enable, false: disable + */ + OB_PROP_SYNC_SIGNAL_TRIGGER_OUT_BOOL = 130, + + /** + * @brief Restore factory settings and factory parameters + * @attention This command can only be written, and the parameter value must be true. The command takes effect after restarting the device. + */ + OB_PROP_RESTORE_FACTORY_SETTINGS_BOOL = 131, + + /** + * @brief Enter recovery mode (flashing mode) when boot the device + * @attention The device will take effect after rebooting with the enable option. After entering recovery mode, you can upgrade the device system. Upgrading + * the system may cause system damage, please use it with caution. + */ + OB_PROP_BOOT_INTO_RECOVERY_MODE_BOOL = 132, + + /** + * @brief Query whether the current device is running in recovery mode (read-only) + */ + OB_PROP_DEVICE_IN_RECOVERY_MODE_BOOL = 133, + + /** + * @brief Capture interval mode, 0:time interval, 1:number interval + */ + OB_PROP_CAPTURE_INTERVAL_MODE_INT = 134, + + /** + * @brief Capture time interval + */ + OB_PROP_CAPTURE_IMAGE_TIME_INTERVAL_INT = 135, + + /** + * @brief Capture number interval + */ + OB_PROP_CAPTURE_IMAGE_NUMBER_INTERVAL_INT = 136, + + /* + * @brief Timer reset function enable + */ + OB_PROP_TIMER_RESET_ENABLE_BOOL = 140, + + /** + * @brief Enable or disable the device to retry USB2.0 re-identification when the device is connected to a USB2.0 port. + * @brief This feature ensures that the device is not mistakenly identified as a USB 2.0 device when connected to a USB 3.0 port. + */ + OB_PROP_DEVICE_USB2_REPEAT_IDENTIFY_BOOL = 141, + + /** + * @brief Reboot device delay mode. Delay time unit: ms, range: [0, 8000). + */ + OB_PROP_DEVICE_REBOOT_DELAY_INT = 142, + + /** + * @brief Query the status of laser overcurrent protection (read-only) + */ + OB_PROP_LASER_OVERCURRENT_PROTECTION_STATUS_BOOL = 148, + + /** + * @brief Query the status of laser pulse width protection (read-only) + */ + OB_PROP_LASER_PULSE_WIDTH_PROTECTION_STATUS_BOOL = 149, + + /** + * @brief Laser always on, true: always on, false: off, laser will be turned off when out of exposure time + */ + OB_PROP_LASER_ALWAYS_ON_BOOL = 174, + + /** + * @brief Laser on/off alternate mode, 0: off, 1: on-off alternate, 2: off-on alternate + * @attention When turn on this mode, the laser will turn on and turn off alternately each frame. + */ + OB_PROP_LASER_ON_OFF_PATTERN_INT = 175, + + /** + * @brief Depth unit flexible adjustment\ + * @brief This property allows continuous adjustment of the depth unit, unlike @ref OB_PROP_DEPTH_PRECISION_LEVEL_INT must be set to some fixed value. + */ + OB_PROP_DEPTH_UNIT_FLEXIBLE_ADJUSTMENT_FLOAT = 176, + + /** + * @brief Laser control, 0: off, 1: on, 2: auto + * + */ + OB_PROP_LASER_CONTROL_INT = 182, + + /** + * @brief IR brightness + */ + OB_PROP_IR_BRIGHTNESS_INT = 184, + + /** + * @brief Slave/secondary device synchronization status (read-only) + */ + OB_PROP_SLAVE_DEVICE_SYNC_STATUS_BOOL = 188, + + /** + * @brief Color AE max exposure + */ + OB_PROP_COLOR_AE_MAX_EXPOSURE_INT = 189, + + /** + * @brief Max exposure time of IR auto exposure + */ + OB_PROP_IR_AE_MAX_EXPOSURE_INT = 190, + + /** + * @brief Disparity search range mode, 1: 128, 2: 256 + */ + OB_PROP_DISP_SEARCH_RANGE_MODE_INT = 191, + + /** + * @brief Laser high temperature protection + */ + OB_PROP_LASER_HIGH_TEMPERATURE_PROTECT_BOOL = 193, + + /** + * @brief low exposure laser control + * + * @brief Currently using for DabaiA device,if the exposure value is lower than a certain threshold, the laser is turned off; + * if it exceeds another threshold, the laser is turned on again. + */ + OB_PROP_LOW_EXPOSURE_LASER_CONTROL_BOOL = 194, + + /** + * @brief check pps sync in signal + */ + OB_PROP_CHECK_PPS_SYNC_IN_SIGNAL_BOOL = 195, + + /** + * @brief Disparity search range offset, range: [0, 127] + */ + OB_PROP_DISP_SEARCH_OFFSET_INT = 196, + + /** + * @brief Repower device (cut off power and power on again) + * + * @brief Currently using for GMSL device, cut off power and power on again by GMSL host driver. + */ + OB_PROP_DEVICE_REPOWER_BOOL = 202, + + /** + * @brief frame interleave config index + */ + OB_PROP_FRAME_INTERLEAVE_CONFIG_INDEX_INT = 204, + + /** + * @brief frame interleave enable (true:enable,false:disable) + */ + OB_PROP_FRAME_INTERLEAVE_ENABLE_BOOL = 205, + /** + * @brief laser pattern sync with delay(us) + */ + OB_PROP_FRAME_INTERLEAVE_LASER_PATTERN_SYNC_DELAY_INT = 206, + /** + * @brief Get the health check result from device,range is [0.0f,1.5f] + */ + OB_PROP_ON_CHIP_CALIBRATION_HEALTH_CHECK_FLOAT = 209, + + /** + * @brief Enable or disable on-chip calibration + */ + OB_PROP_ON_CHIP_CALIBRATION_ENABLE_BOOL = 210, + + /** + * @brief hardware noise remove filter switch + */ + OB_PROP_HW_NOISE_REMOVE_FILTER_ENABLE_BOOL = 211, + /** + * @brief hardware noise remove filter threshold ,range [0.0 - 1.0] + */ + OB_PROP_HW_NOISE_REMOVE_FILTER_THRESHOLD_FLOAT = 212, + /** + * @brief soft trigger auto capture enable, use in OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING mode + */ + OB_DEVICE_AUTO_CAPTURE_ENABLE_BOOL = 216, + /** + * @brief soft trigger auto capture interval time, use in OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING mode + */ + OB_DEVICE_AUTO_CAPTURE_INTERVAL_TIME_INT = 217, + + /** + * @brief PTP time synchronization enable + */ + OB_DEVICE_PTP_CLOCK_SYNC_ENABLE_BOOL = 223, + + /** + * @brief Depth with confidence stream enable + */ + OB_PROP_DEPTH_WITH_CONFIDENCE_STREAM_ENABLE_BOOL = 224, + + /** + * @brief Enable or disable confidence stream filter + */ + OB_PROP_CONFIDENCE_STREAM_FILTER_BOOL = 226, + + /** + * @brief Confidence stream filter threshold, range [0, 255] + */ + OB_PROP_CONFIDENCE_STREAM_FILTER_THRESHOLD_INT = 227, + + /** + * @brief Confidence stream mirror enable + */ + OB_PROP_CONFIDENCE_MIRROR_BOOL = 229, + + /** + * @brief Confidence stream flip enable + */ + OB_PROP_CONFIDENCE_FLIP_BOOL = 230, + + /** + * @brief Confidence stream rotate angle{0, 90, 180, 270} + */ + OB_PROP_CONFIDENCE_ROTATE_INT = 231, + + /** + * @brief Intra-camera Sync Reference based on the exposure start time, the exposure middle time, or the exposure end time. the definition in @ref + * OBIntraCameraSyncReference + */ + OB_PROP_INTRA_CAMERA_SYNC_REFERENCE_INT = 236, + + /** + * @brief Baseline calibration parameters + */ + OB_STRUCT_BASELINE_CALIBRATION_PARAM = 1002, + + /** + * @brief Device temperature information + */ + OB_STRUCT_DEVICE_TEMPERATURE = 1003, + + /** + * @brief TOF exposure threshold range + */ + OB_STRUCT_TOF_EXPOSURE_THRESHOLD_CONTROL = 1024, + + /** + * @brief get/set serial number + */ + OB_STRUCT_DEVICE_SERIAL_NUMBER = 1035, + + /** + * @brief get/set device time + */ + OB_STRUCT_DEVICE_TIME = 1037, + + /** + * @brief Multi-device synchronization mode and parameter configuration + */ + OB_STRUCT_MULTI_DEVICE_SYNC_CONFIG = 1038, + + /** + * @brief RGB cropping ROI + */ + OB_STRUCT_RGB_CROP_ROI = 1040, + + /** + * @brief Device IP address configuration + */ + OB_STRUCT_DEVICE_IP_ADDR_CONFIG = 1041, + + /** + * @brief The current camera depth mode + */ + OB_STRUCT_CURRENT_DEPTH_ALG_MODE = 1043, + + /** + * @brief A list of depth accuracy levels, returning an array of uin16_t, corresponding to the enumeration + */ + OB_STRUCT_DEPTH_PRECISION_SUPPORT_LIST = 1045, + + /** + * @brief Device network static ip config record + * @brief Using for get last static ip config, witch is record in device flash when user set static ip config + * + * @attention read only + */ + OB_STRUCT_DEVICE_STATIC_IP_CONFIG_RECORD = 1053, + + /** + * @brief Using to configure the depth sensor's HDR mode + * @brief The Value type is @ref OBHdrConfig + * + * @attention After enable HDR mode, the depth sensor auto exposure will be disabled. + */ + OB_STRUCT_DEPTH_HDR_CONFIG = 1059, + + /** + * @brief Color Sensor AE ROI configuration + * @brief The Value type is @ref OBRegionOfInterest + */ + OB_STRUCT_COLOR_AE_ROI = 1060, + + /** + * @brief Depth Sensor AE ROI configuration + * @brief The Value type is @ref OBRegionOfInterest + * @brief Since the ir sensor is the same physical sensor as the depth sensor, this property will also effect the ir sensor. + */ + OB_STRUCT_DEPTH_AE_ROI = 1061, + + /** + * @brief ASIC serial number + */ + OB_STRUCT_ASIC_SERIAL_NUMBER = 1063, + + /** + * @brief Disparity offset interleaving + */ + OB_STRUCT_DISP_OFFSET_CONFIG = 1064, + + /** + * @brief Preset resolution ratio configuration + */ + OB_STRUCT_PRESET_RESOLUTION_CONFIG = 1069, + + /** + * @brief Color camera auto exposure + */ + OB_PROP_COLOR_AUTO_EXPOSURE_BOOL = 2000, + + /** + * @brief Color camera exposure adjustment + */ + OB_PROP_COLOR_EXPOSURE_INT = 2001, + + /** + * @brief Color camera gain adjustment + */ + OB_PROP_COLOR_GAIN_INT = 2002, + + /** + * @brief Color camera automatic white balance + */ + OB_PROP_COLOR_AUTO_WHITE_BALANCE_BOOL = 2003, + + /** + * @brief Color camera white balance adjustment + */ + OB_PROP_COLOR_WHITE_BALANCE_INT = 2004, + + /** + * @brief Color camera brightness adjustment + */ + OB_PROP_COLOR_BRIGHTNESS_INT = 2005, + + /** + * @brief Color camera sharpness adjustment + */ + OB_PROP_COLOR_SHARPNESS_INT = 2006, + + /** + * @brief Color camera shutter adjustment + */ + OB_PROP_COLOR_SHUTTER_INT = 2007, + + /** + * @brief Color camera saturation adjustment + */ + OB_PROP_COLOR_SATURATION_INT = 2008, + + /** + * @brief Color camera contrast adjustment + */ + OB_PROP_COLOR_CONTRAST_INT = 2009, + + /** + * @brief Color camera gamma adjustment + */ + OB_PROP_COLOR_GAMMA_INT = 2010, + + /** + * @brief Color camera image rotation + */ + OB_PROP_COLOR_ROLL_INT = 2011, + + /** + * @brief Color camera auto exposure priority + */ + OB_PROP_COLOR_AUTO_EXPOSURE_PRIORITY_INT = 2012, + + /** + * @brief Color camera brightness compensation + */ + OB_PROP_COLOR_BACKLIGHT_COMPENSATION_INT = 2013, + + /** + * @brief Color camera color tint + */ + OB_PROP_COLOR_HUE_INT = 2014, + + /** + * @brief Color Camera Power Line Frequency + */ + OB_PROP_COLOR_POWER_LINE_FREQUENCY_INT = 2015, + + /** + * @brief Automatic exposure of depth camera (infrared camera will be set synchronously under some models of devices) + */ + OB_PROP_DEPTH_AUTO_EXPOSURE_BOOL = 2016, + + /** + * @brief Depth camera exposure adjustment (infrared cameras will be set synchronously under some models of devices) + */ + OB_PROP_DEPTH_EXPOSURE_INT = 2017, + + /** + * @brief Depth camera gain adjustment (infrared cameras will be set synchronously under some models of devices) + */ + OB_PROP_DEPTH_GAIN_INT = 2018, + + /** + * @brief Infrared camera auto exposure (depth camera will be set synchronously under some models of devices) + */ + OB_PROP_IR_AUTO_EXPOSURE_BOOL = 2025, + + /** + * @brief Infrared camera exposure adjustment (some models of devices will set the depth camera synchronously) + */ + OB_PROP_IR_EXPOSURE_INT = 2026, + + /** + * @brief Infrared camera gain adjustment (the depth camera will be set synchronously under some models of devices) + */ + OB_PROP_IR_GAIN_INT = 2027, + + /** + * @brief Select Infrared camera data source channel. If not support throw exception. 0 : IR stream from IR Left sensor; 1 : IR stream from IR Right sensor; + */ + OB_PROP_IR_CHANNEL_DATA_SOURCE_INT = 2028, + + /** + * @brief Depth effect dedistortion, true: on, false: off. mutually exclusive with D2C function, RM_Filter disable When hardware or software D2C is enabled. + */ + OB_PROP_DEPTH_RM_FILTER_BOOL = 2029, + + /** + * @brief Color camera maximal gain + */ + OB_PROP_COLOR_MAXIMAL_GAIN_INT = 2030, + + /** + * @brief Color camera shutter gain + */ + OB_PROP_COLOR_MAXIMAL_SHUTTER_INT = 2031, + + /** + * @brief The enable/disable switch for IR short exposure function, supported only by a few devices. + */ + OB_PROP_IR_SHORT_EXPOSURE_BOOL = 2032, + + /** + * @brief Color camera HDR + */ + OB_PROP_COLOR_HDR_BOOL = 2034, + + /** + * @brief IR long exposure mode switch read and write. + */ + OB_PROP_IR_LONG_EXPOSURE_BOOL = 2035, + + /** + * @brief Setting and getting the USB device frame skipping mode status, true: frame skipping mode, false: non-frame skipping mode. + */ + OB_PROP_SKIP_FRAME_BOOL = 2036, + + /** + * @brief Depth HDR merge, true: on, false: off. + */ + OB_PROP_HDR_MERGE_BOOL = 2037, + + /** + * @brief Color camera FOCUS + */ + OB_PROP_COLOR_FOCUS_INT = 2038, + /** + * @brief ir rectify status,true: ir rectify, false: no rectify + */ + OB_PROP_IR_RECTIFY_BOOL = 2040, + + /** + * @brief Depth camera priority + * + */ + OB_PROP_DEPTH_AUTO_EXPOSURE_PRIORITY_INT = 2052, + + /** + * @brief Software disparity to depth + */ + OB_PROP_SDK_DISPARITY_TO_DEPTH_BOOL = 3004, + + /** + * @brief Depth data unpacking function switch (each open stream will be turned on by default, support RLE/Y10/Y11/Y12/Y14 format) + */ + OB_PROP_SDK_DEPTH_FRAME_UNPACK_BOOL = 3007, + + /** + * @brief IR data unpacking function switch (each current will be turned on by default, support RLE/Y10/Y11/Y12/Y14 format) + */ + OB_PROP_SDK_IR_FRAME_UNPACK_BOOL = 3008, + + /** + * @brief Accel data conversion function switch (on by default) + */ + OB_PROP_SDK_ACCEL_FRAME_TRANSFORMED_BOOL = 3009, + + /** + * @brief Gyro data conversion function switch (on by default) + */ + OB_PROP_SDK_GYRO_FRAME_TRANSFORMED_BOOL = 3010, + + /** + * @brief Left IR frame data unpacking function switch (each current will be turned on by default, support RLE/Y10/Y11/Y12/Y14 format) + */ + OB_PROP_SDK_IR_LEFT_FRAME_UNPACK_BOOL = 3011, + + /** + * @brief Right IR frame data unpacking function switch (each current will be turned on by default, support RLE/Y10/Y11/Y12/Y14 format) + */ + OB_PROP_SDK_IR_RIGHT_FRAME_UNPACK_BOOL = 3012, + + /** + * @brief Depth Stream Industry Working Mode Settings, currently only supported by DCW2. + */ + OB_PROP_DEPTH_INDUSTRY_MODE_INT = 3024, + + /** + * @brief Read the current network bandwidth type of the network device, whether it is Gigabit Ethernet or Fast Ethernet, such as G335LE. + */ + OB_PROP_NETWORK_BANDWIDTH_TYPE_INT = 3027, + + /** + * @brief Switch device performance mode, currently available in Adaptive Mode and High Performance Mode, such as G335LE. + */ + OB_PROP_DEVICE_PERFORMANCE_MODE_INT = 3028, + + /** + * @brief Calibration JSON file read from device (Femto Mega, read only) + */ + OB_RAW_DATA_CAMERA_CALIB_JSON_FILE = 4029, + + /** + * @brief Confidence degree + */ + OB_PROP_DEBUG_ESGM_CONFIDENCE_FLOAT = 5013, + + /** + * @brief Color camera CCI denoising level. 0: Auto; 1-8: higher values indicate stronger denoising. + * @note This setting has no effect when AE (Auto Exposure) is disabled. + */ + OB_PROP_COLOR_DENOISING_LEVEL_INT = 5525, +} OBPropertyID, + ob_property_id; + +// For backward compatibility +#define OB_PROP_TIMER_RESET_TRIGGLE_OUT_ENABLE_BOOL OB_PROP_TIMER_RESET_TRIGGER_OUT_ENABLE_BOOL +#define OB_PROP_LASER_ON_OFF_MODE_INT OB_PROP_LASER_ON_OFF_PATTERN_INT +#define OB_PROP_LASER_ENERGY_LEVEL_INT OB_PROP_LASER_POWER_LEVEL_CONTROL_INT +#define OB_PROP_LASER_HW_ENERGY_LEVEL_INT OB_PROP_LASER_POWER_ACTUAL_LEVEL_INT +#define OB_PROP_DEVICE_USB3_REPEAT_IDENTIFY_BOOL OB_PROP_DEVICE_USB2_REPEAT_IDENTIFY_BOOL +#define OB_PROP_DEPTH_SOFT_FILTER_BOOL OB_PROP_DEPTH_NOISE_REMOVAL_FILTER_BOOL +#define OB_PROP_DEPTH_MAX_DIFF_INT OB_PROP_DEPTH_NOISE_REMOVAL_FILTER_MAX_DIFF_INT +#define OB_PROP_DEPTH_MAX_SPECKLE_SIZE_INT OB_PROP_DEPTH_NOISE_REMOVAL_FILTER_MAX_SPECKLE_SIZE_INT + +/** + * @brief The data type used to describe all property settings + */ +typedef enum OBPropertyType { + OB_BOOL_PROPERTY = 0, /**< Boolean property */ + OB_INT_PROPERTY = 1, /**< Integer property */ + OB_FLOAT_PROPERTY = 2, /**< Floating-point property */ + OB_STRUCT_PROPERTY = 3, /**< Struct property */ +} OBPropertyType, + ob_property_type; + +/** + * @brief Used to describe the characteristics of each property + */ +typedef struct OBPropertyItem { + OBPropertyID id; /**< Property ID */ + const char *name; /**< Property name */ + OBPropertyType type; /**< Property type */ + OBPermissionType permission; /**< Property read and write permission */ +} OBPropertyItem, ob_property_item; + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/RecordPlayback.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/RecordPlayback.h new file mode 100644 index 0000000..7b93ff3 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/RecordPlayback.h @@ -0,0 +1,135 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file RecordPlayback.hpp + * @brief Record and playback device-related types, including interfaces to create recording and playback devices, + record and playback streaming data, etc. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "Device.h" + +/** + * @brief Create a recording device for the specified device with a specified file path and compression enabled. + * + * @param[in] device The device to record. + * @param[in] file_path The file path to record to. + * @param[in] compression_enabled Whether to enable compression for the recording. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return A pointer to the newly created recording device, or NULL if an error occurred. + */ +OB_EXPORT ob_record_device *ob_create_record_device(ob_device *device, const char *file_path, bool compression_enabled, ob_error **error); + +/** + * @brief Delete a recording device. + * + * @param[in] recorder The recording device to delete. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_record_device(ob_record_device *recorder, ob_error **error); + +/** + * @brief Pause recording on the specified recording device. + * + * @param[in] recorder The recording device to pause. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_record_device_pause(ob_record_device *recorder, ob_error **error); + +/** + * @brief Resume recording on the specified recording device. + * + * @param[in] recorder The recording device to resume. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_record_device_resume(ob_record_device *recorder, ob_error **error); + +/** + * @brief Create a playback device for the specified file path. + * + * @param[in] file_path The file path to playback from. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return A pointer to the newly created playback device, or NULL if an error occurred. + */ +OB_EXPORT ob_device *ob_create_playback_device(const char *file_path, ob_error **error); + +/** + * @brief Pause playback on the specified playback device. + * + * @param[in] player The playback device to pause. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_playback_device_pause(ob_device *player, ob_error **error); + +/** + * @brief Resume playback on the specified playback device. + * + * @param[in] player The playback device to resume. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_playback_device_resume(ob_device *player, ob_error **error); + +/** + * @brief Set the playback to a specified time point of the played data. + * + * @param[in] player The playback device to set the position for. + * @param[in] timestamp The position to set the playback to, in milliseconds. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_playback_device_seek(ob_device *player, const uint64_t timestamp, ob_error **error); + +/** + * @brief Set the playback to a specified time point of the played data. + * + * @param[in] player The playback device to set the position for. + * @param[in] rate The playback rate to set. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_playback_device_set_playback_rate(ob_device *player, const float rate, ob_error **error); + +/** + * @brief Get the current playback status of the played data. + * + * @param[in] player The playback device to get the status for. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The current playback status of the played data. + */ +OB_EXPORT ob_playback_status ob_playback_device_get_current_playback_status(ob_device *player, ob_error **error); + +/** + * @brief Set a callback function to receive playback status updates. + * + * @param[in] player The playback device to set the callback for. + * @param[in] callback The callback function to receive playback status updates. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_playback_device_set_playback_status_changed_callback(ob_device *player, ob_playback_status_changed_callback callback, void *user_data, + ob_error **error); + +/** + * @brief Get the current playback position of the played data. + * + * @param[in] player The playback device to get the position for. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The current playback position of the played data, in milliseconds. + */ +OB_EXPORT uint64_t ob_playback_device_get_position(ob_device *player, ob_error **error); + +/** + * @brief Get the duration of the played data. + * + * @param[in] player The playback device to get the duration for. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The duration of the played data, in milliseconds. + */ +OB_EXPORT uint64_t ob_playback_device_get_duration(ob_device *player, ob_error **error); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Sensor.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Sensor.h new file mode 100644 index 0000000..b758779 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Sensor.h @@ -0,0 +1,132 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Sensor.h + * @brief Defines types related to sensors, used for obtaining stream configurations, opening and closing streams, and setting and getting sensor properties. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Get the type of the sensor. + * + * @param[in] sensor The sensor object. + * @param[out] error Logs error messages. + * @return The sensor type. + */ +OB_EXPORT ob_sensor_type ob_sensor_get_type(const ob_sensor *sensor, ob_error **error); + +/** + * @brief Get a list of all supported stream profiles. + * + * @param[in] sensor The sensor object. + * @param[out] error Logs error messages. + * @return A list of stream profiles. + */ +OB_EXPORT ob_stream_profile_list *ob_sensor_get_stream_profile_list(const ob_sensor *sensor, ob_error **error); + +/** + * @brief Open the current sensor and set the callback data frame. + * + * @param[in] sensor The sensor object. + * @param[in] profile The stream configuration information. + * @param[in] callback The callback function triggered when frame data arrives. + * @param[in] user_data Any user data to pass in and get from the callback. + * @param[out] error Logs error messages. + */ +OB_EXPORT void ob_sensor_start(ob_sensor *sensor, const ob_stream_profile *profile, ob_frame_callback callback, void *user_data, ob_error **error); + +/** + * @brief Stop the sensor stream. + * + * @param[in] sensor The sensor object. + * @param[out] error Logs error messages. + */ +OB_EXPORT void ob_sensor_stop(ob_sensor *sensor, ob_error **error); + +/** + * @brief Switch resolutions. + * + * @param[in] sensor The sensor object. + * @param[in] profile The stream configuration information. + * @param[out] error Logs error messages. + */ +OB_EXPORT void ob_sensor_switch_profile(ob_sensor *sensor, ob_stream_profile *profile, ob_error **error); + +/** + * @brief Delete a sensor object. + * + * @param[in] sensor The sensor object to delete. + * @param[out] error Logs error messages. + */ +OB_EXPORT void ob_delete_sensor(ob_sensor *sensor, ob_error **error); + +/** + * @brief Create a list of recommended filters for the specified sensor. + * + * @param[in] sensor The ob_sensor object. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return ob_filter_list + */ +OB_EXPORT ob_filter_list *ob_sensor_create_recommended_filter_list(const ob_sensor *sensor, ob_error **error); + +/** + * @brief Get the number of sensors in the sensor list. + * + * @param[in] sensor_list The list of sensor objects. + * @param[out] error Logs error messages. + * @return The number of sensors in the list. + */ +OB_EXPORT uint32_t ob_sensor_list_get_count(const ob_sensor_list *sensor_list, ob_error **error); + +/** + * @brief Get the sensor type. + * + * @param[in] sensor_list The list of sensor objects. + * @param[in] index The index of the sensor on the list. + * @param[out] error Logs error messages. + * @return The sensor type. + */ +OB_EXPORT ob_sensor_type ob_sensor_list_get_sensor_type(const ob_sensor_list *sensor_list, uint32_t index, ob_error **error); + +/** + * @brief Get a sensor by sensor type. + * + * @param[in] sensor_list The list of sensor objects. + * @param[in] sensorType The sensor type to be obtained. + * @param[out] error Logs error messages. + * @return The sensor pointer. If the specified type of sensor does not exist, it will return null. + */ +OB_EXPORT ob_sensor *ob_sensor_list_get_sensor_by_type(const ob_sensor_list *sensor_list, ob_sensor_type sensorType, ob_error **error); + +/** + * @brief Get a sensor by index number. + * + * @param[in] sensor_list The list of sensor objects. + * @param[in] index The index of the sensor on the list. + * @param[out] error Logs error messages. + * @return The sensor object. + */ +OB_EXPORT ob_sensor *ob_sensor_list_get_sensor(const ob_sensor_list *sensor_list, uint32_t index, ob_error **error); + +/** + * @brief Delete a list of sensor objects. + * + * @param[in] sensor_list The list of sensor objects to delete. + * @param[out] error Logs error messages. + */ +OB_EXPORT void ob_delete_sensor_list(ob_sensor_list *sensor_list, ob_error **error); + +#define ob_sensor_list_get_sensor_count ob_sensor_list_get_count +#define ob_sensor_get_recommended_filter_list ob_sensor_create_recommended_filter_list + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/StreamProfile.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/StreamProfile.h new file mode 100644 index 0000000..98e5ad1 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/StreamProfile.h @@ -0,0 +1,417 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file StreamProfile.h + * @brief The stream profile related type is used to get information such as the width, height, frame rate, and format of the stream. + * + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Create a stream profile object + * + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_stream_profile* return the stream profile object + */ +OB_EXPORT ob_stream_profile *ob_create_stream_profile(ob_stream_type type, ob_format format, ob_error **error); + +/** + * @brief Create a video stream profile object + * + * @param[in] type Stream type + * @param[in] format Stream format + * @param[in] width Stream width + * @param[in] height Stream height + * @param[in] fps Stream frame rate + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_stream_profile* return the video stream profile object + */ +OB_EXPORT ob_stream_profile *ob_create_video_stream_profile(ob_stream_type type, ob_format format, uint32_t width, uint32_t height, uint32_t fps, + ob_error **error); + +/** + * @brief Create a accel stream profile object + * + * @param[in] full_scale_range Accel full scale range + * @param[in] sample_rate Accel sample rate + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_stream_profile* return the accel stream profile object + */ +OB_EXPORT ob_stream_profile *ob_create_accel_stream_profile(ob_accel_full_scale_range full_scale_range, ob_accel_sample_rate sample_rate, ob_error **error); + +/** + * @brief Create a gyro stream profile object + * + * @param[in] full_scale_range Gyro full scale range + * @param[in] sample_rate Gyro sample rate + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_stream_profile* return the accel stream profile object + */ +OB_EXPORT ob_stream_profile *ob_create_gyro_stream_profile(ob_gyro_full_scale_range full_scale_range, ob_gyro_sample_rate sample_rate, ob_error **error); + +/** + * @brief Copy the stream profile object from an other stream profile object + * + * @param[in] srcProfile Source stream profile object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return ob_stream_profile* return the new stream profile object + */ +OB_EXPORT ob_stream_profile *ob_create_stream_profile_from_other_stream_profile(const ob_stream_profile *srcProfile, ob_error **error); + +/** + * @brief Copy the stream profile object with a new format object + * + * @param[in] profile Stream profile object + * @param[in] new_format New format + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return ob_stream_profile* return the new stream profile object with the new format + */ +OB_EXPORT ob_stream_profile *ob_create_stream_profile_with_new_format(const ob_stream_profile *profile, ob_format new_format, ob_error **error); + +/** + * @brief Delete the stream configuration. + * + * @param[in] profile Stream profile object . + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_stream_profile(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Get stream profile format + * + * @param[in] profile Stream profile object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_format return the format of the stream + */ +OB_EXPORT ob_format ob_stream_profile_get_format(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Set stream profile format + * + * @param[in] profile Stream profile object + * @param[in] format The format of the stream + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_stream_profile_set_format(ob_stream_profile *profile, ob_format format, ob_error **error); + +/** + * @brief Get stream profile type + * + * @param[in] profile Stream profile object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_stream_type stream type + */ +OB_EXPORT ob_stream_type ob_stream_profile_get_type(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Set stream profile type + * + * @param[in] profile Stream profile object + * @param[in] type The type of the stream + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_stream_profile_set_type(const ob_stream_profile *profile, ob_stream_type type, ob_error **error); + +/** + * @brief Get the extrinsic for source stream to target stream + * + * @param[in] source Source stream profile + * @param[in] target Target stream profile + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_extrinsic The extrinsic + */ +OB_EXPORT ob_extrinsic ob_stream_profile_get_extrinsic_to(const ob_stream_profile *source, ob_stream_profile *target, ob_error **error); + +/** + * @brief Set the extrinsic for source stream to target stream + * + * @param[in] source Stream profile object + * @param[in] target Target stream type + * @param[in] extrinsic The extrinsic + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_stream_profile_set_extrinsic_to(ob_stream_profile *source, const ob_stream_profile *target, ob_extrinsic extrinsic, ob_error **error); + +/** + * @brief Set the extrinsic for source stream to target stream type + * + * @param[in] source Source stream profile + * @param[in] type Target stream type + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_extrinsic The extrinsic + */ +OB_EXPORT void ob_stream_profile_set_extrinsic_to_type(ob_stream_profile *source, const ob_stream_type type, ob_extrinsic extrinsic, ob_error **error); + +/** + * @brief Get the frame rate of the video stream + * + * @param[in] profile Stream profile object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the frame rate of the stream + */ +OB_EXPORT uint32_t ob_video_stream_profile_get_fps(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Get the width of the video stream + * + * @param[in] profile Stream profile object , If the profile is not a video stream configuration, an error will be returned + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the width of the stream + */ +OB_EXPORT uint32_t ob_video_stream_profile_get_width(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Set the width of the video stream + * + * @param[in] profile Stream profile object , If the profile is not a video stream configuration, an error will be returned + * @param[in] width The width of the stream + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_video_stream_profile_set_width(ob_stream_profile *profile, uint32_t width, ob_error **error); + +/** + * @brief Get the height of the video stream + * + * @param[in] profile Stream profile object , If the profile is not a video stream configuration, an error will be returned + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return uint32_t return the height of the stream + */ +OB_EXPORT uint32_t ob_video_stream_profile_get_height(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Set the height of the video stream + * + * @param[in] profile Stream profile object , If the profile is not a video stream configuration, an error will be returned + * @param[in] height The height of the stream + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_video_stream_profile_set_height(ob_stream_profile *profile, uint32_t height, ob_error **error); + +/** + * @brief Get the intrinsic of the video stream profile + * + * @param[in] profile Stream profile object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_camera_intrinsic Return the intrinsic of the stream + */ +OB_EXPORT ob_camera_intrinsic ob_video_stream_profile_get_intrinsic(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Set the intrinsic of the video stream profile + * + * @param[in] profile Stream profile object + * @param[in] intrinsic The intrinsic of the stream + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_video_stream_profile_set_intrinsic(ob_stream_profile *profile, ob_camera_intrinsic intrinsic, ob_error **error); + +/** + * @brief Get the distortion of the video stream profile + * + * @param[in] profile Stream profile object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_camera_distortion Return the distortion of the stream + */ +OB_EXPORT ob_camera_distortion ob_video_stream_profile_get_distortion(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Set the distortion of the video stream profile + * + * @param[in] profile Stream profile object + * @param[in] distortion The distortion of the stream + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_video_stream_profile_set_distortion(ob_stream_profile *profile, ob_camera_distortion distortion, ob_error **error); + +/** + * @brief Get the process param of the disparity stream + * + * @param[in] profile Stream profile object + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_disparity_param Return the disparity process param of the stream + */ +OB_EXPORT ob_disparity_param ob_disparity_based_stream_profile_get_disparity_param(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Set the disparity process param of the disparity stream. + * + * @param[in] profile Stream profile object. If the profile is not for the disparity stream, an error will be returned. + * @param[in] param The disparity process param of the disparity stream. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_disparity_based_stream_profile_set_disparity_param(ob_stream_profile *profile, ob_disparity_param param, ob_error **error); + +/** + * @brief Get the full-scale range of the accelerometer stream. + * + * @param[in] profile Stream profile object. If the profile is not for the accelerometer stream, an error will be returned. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The full-scale range of the accelerometer stream. + */ +OB_EXPORT ob_accel_full_scale_range ob_accel_stream_profile_get_full_scale_range(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Get the sampling frequency of the accelerometer frame. + * + * @param[in] profile Stream profile object. If the profile is not for the accelerometer stream, an error will be returned. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The sampling frequency of the accelerometer frame. + */ +OB_EXPORT ob_accel_sample_rate ob_accel_stream_profile_get_sample_rate(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Get the intrinsic of the accelerometer stream. + * + * @param[in] profile Stream profile object. If the profile is not for the accelerometer stream, an error will be returned. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_accel_intrinsic Return the intrinsic of the accelerometer stream. + */ +OB_EXPORT ob_accel_intrinsic ob_accel_stream_profile_get_intrinsic(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Set the intrinsic of the accelerometer stream. + * + * @param[in] profile Stream profile object. If the profile is not for the accelerometer stream, an error will be returned. + * @param[in] intrinsic The intrinsic of the accelerometer stream. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_accel_stream_profile_set_intrinsic(ob_stream_profile *profile, ob_accel_intrinsic intrinsic, ob_error **error); + +/** + * @brief Get the full-scale range of the gyroscope stream. + * + * @param[in] profile Stream profile object. If the profile is not for the gyroscope stream, an error will be returned. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The full-scale range of the gyroscope stream. + */ +OB_EXPORT ob_gyro_full_scale_range ob_gyro_stream_profile_get_full_scale_range(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Get the sampling frequency of the gyroscope stream. + * + * @param[in] profile Stream profile object. If the profile is not for the gyroscope stream, an error will be returned. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The sampling frequency of the gyroscope stream. + */ +OB_EXPORT ob_gyro_sample_rate ob_gyro_stream_profile_get_sample_rate(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Get the intrinsic of the gyroscope stream. + * + * @param[in] profile Stream profile object. If the profile is not for the gyroscope stream, an error will be returned. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return ob_gyro_intrinsic Return the intrinsic of the gyroscope stream. + */ +OB_EXPORT ob_gyro_intrinsic ob_gyro_stream_get_intrinsic(const ob_stream_profile *profile, ob_error **error); + +/** + * @brief Set the intrinsic of the gyroscope stream. + * + * @param[in] profile Stream profile object. If the profile is not for the gyroscope stream, an error will be returned. + * @param[in] intrinsic The intrinsic of the gyroscope stream. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_gyro_stream_set_intrinsic(ob_stream_profile *profile, ob_gyro_intrinsic intrinsic, ob_error **error); + +/** + * @brief Get the number of StreamProfile lists. + * + * @param[in] profile_list StreamProfile list. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The number of StreamProfile lists. + */ +OB_EXPORT uint32_t ob_stream_profile_list_get_count(const ob_stream_profile_list *profile_list, ob_error **error); + +/** + * @brief Get the corresponding StreamProfile by subscripting. + * + * @attention The stream profile returned by this function should be deleted by calling @ref ob_delete_stream_profile() when it is no longer needed. + * + * @param[in] profile_list StreamProfile lists. + * @param[in] index Index. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The matching profile. + */ +OB_EXPORT ob_stream_profile *ob_stream_profile_list_get_profile(const ob_stream_profile_list *profile_list, int index, ob_error **error); + +/** + * @brief Match the corresponding ob_stream_profile through the passed parameters. If there are multiple matches, + * the first one in the list will be returned by default. If no matched profile is found, an error will be returned. + * + * @attention The stream profile returned by this function should be deleted by calling @ref ob_delete_stream_profile() when it is no longer needed. + * + * @param[in] profile_list Resolution list. + * @param[in] width Width. If you don't need to add matching conditions, you can pass OB_WIDTH_ANY. + * @param[in] height Height. If you don't need to add matching conditions, you can pass OB_HEIGHT_ANY. + * @param[in] format Format. If you don't need to add matching conditions, you can pass OB_FORMAT_ANY. + * @param[in] fps Frame rate. If you don't need to add matching conditions, you can pass OB_FPS_ANY. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The matching profile. + */ +OB_EXPORT ob_stream_profile *ob_stream_profile_list_get_video_stream_profile(const ob_stream_profile_list *profile_list, int width, int height, + ob_format format, int fps, ob_error **error); + +/** + * @brief Match the corresponding ob_stream_profile through the passed parameters. If there are multiple matches, + * the first one in the list will be returned by default. If no matched profile is found, an error will be returned. + * + * @attention The stream profile returned by this function should be deleted by calling @ref ob_delete_stream_profile() when it is no longer needed. + * + * @param[in] profile_list Resolution list. + * @param[in] full_scale_range Full-scale range. If you don't need to add matching conditions, you can pass 0. + * @param[in] sample_rate Sample rate. If you don't need to add matching conditions, you can pass 0. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The matching profile. + */ +OB_EXPORT ob_stream_profile *ob_stream_profile_list_get_accel_stream_profile(const ob_stream_profile_list *profile_list, + ob_accel_full_scale_range full_scale_range, ob_accel_sample_rate sample_rate, + ob_error **error); + +/** + * @brief Match the corresponding ob_stream_profile through the passed parameters. If there are multiple matches, + * the first one in the list will be returned by default. If no matched profile is found, an error will be returned. + * + * @attention The stream profile returned by this function should be deleted by calling @ref ob_delete_stream_profile() when it is no longer needed. + * + * @param[in] profile_list Resolution list. + * @param[in] full_scale_range Full-scale range. If you don't need to add matching conditions, you can pass 0. + * @param[in] sample_rate Sample rate. If you don't need to add matching conditions, you can pass 0. + * @param[out] error Pointer to an error object that will be set if an error occurs. + * @return The matching profile. + */ +OB_EXPORT ob_stream_profile *ob_stream_profile_list_get_gyro_stream_profile(const ob_stream_profile_list *profile_list, + ob_gyro_full_scale_range full_scale_range, ob_gyro_sample_rate sample_rate, + ob_error **error); + +/** + * @brief Delete the stream profile list. + * + * @param[in] profile_list Stream configuration list. + * @param[out] error Pointer to an error object that will be set if an error occurs. + */ +OB_EXPORT void ob_delete_stream_profile_list(const ob_stream_profile_list *profile_list, ob_error **error); + +// The following interfaces are deprecated and are retained here for compatibility purposes. +#define ob_stream_profile_format ob_stream_profile_get_format +#define ob_stream_profile_type ob_stream_profile_get_type +#define ob_video_stream_profile_fps ob_video_stream_profile_get_fps +#define ob_video_stream_profile_width ob_video_stream_profile_get_width +#define ob_video_stream_profile_height ob_video_stream_profile_get_height +#define ob_accel_stream_profile_full_scale_range ob_accel_stream_profile_get_full_scale_range +#define ob_accel_stream_profile_sample_rate ob_accel_stream_profile_get_sample_rate +#define ob_gyro_stream_profile_full_scale_range ob_gyro_stream_profile_get_full_scale_range +#define ob_gyro_stream_profile_sample_rate ob_gyro_stream_profile_get_sample_rate +#define ob_stream_profile_list_count ob_stream_profile_list_get_count + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/TypeHelper.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/TypeHelper.h new file mode 100644 index 0000000..c71328f --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/TypeHelper.h @@ -0,0 +1,93 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Convert OBFormat to " char* " type and then return. + * + * @param[in] type OBFormat type. + * @return OBFormat of "char*" type. + */ +OB_EXPORT const char* ob_format_type_to_string(OBFormat type); + +/** + * @brief Convert OBFrameType to " char* " type and then return. + * + * @param[in] type OBFrameType type. + * @return OBFrameType of "char*" type. + */ +OB_EXPORT const char* ob_frame_type_to_string(OBFrameType type); + +/** + * @brief Convert OBStreamType to " char* " type and then return. + * + * @param[in] type OBStreamType type. + * @return OBStreamType of "char*" type. + */ +OB_EXPORT const char* ob_stream_type_to_string(OBStreamType type); + +/** + * @brief Convert OBSensorType to " char* " type and then return. + * + * @param[in] type OBSensorType type. + * @return OBSensorType of "char*" type. + */ +OB_EXPORT const char* ob_sensor_type_to_string(OBSensorType type); + +/** + * @brief Convert OBIMUSampleRate to " char* " type and then return. + * + * @param[in] type OBIMUSampleRate type. + * @return OBIMUSampleRate of "char*" type. + */ +OB_EXPORT const char* ob_imu_rate_type_to_string(OBIMUSampleRate type); + +/** + * @brief Convert OBGyroFullScaleRange to " char* " type and then return. + * + * @param[in] type OBGyroFullScaleRange type. + * @return OBGyroFullScaleRange of "char*" type. + */ +OB_EXPORT const char* ob_gyro_range_type_to_string(OBGyroFullScaleRange type); + +/** + * @brief Convert OBAccelFullScaleRange to " char* " type and then return. + * + * @param[in] type OBAccelFullScaleRange type. + * @return OBAccelFullScaleRange of "char*" type. + */ +OB_EXPORT const char* ob_accel_range_type_to_string(OBAccelFullScaleRange type); + +/** + * @brief Convert OBFrameMetadataType to " char* " type and then return. + * + * @param[in] type OBFrameMetadataType type. + * @return OBFrameMetadataType of "char*" type. + */ +OB_EXPORT const char* ob_meta_data_type_to_string(OBFrameMetadataType type); + +/** + * @brief Convert OBStreamType to OBSensorType. + * + * @param[in] type The sensor type to convert. + * @return OBStreamType The corresponding stream type. + */ +OB_EXPORT OBStreamType ob_sensor_type_to_stream_type(OBSensorType type); + +/** + * @brief Convert OBFormat to " char* " type and then return. + * + * @param format The OBFormat to convert. + * @return The string. + */ +OB_EXPORT const char *ob_format_to_string(OBFormat format); +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Utils.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Utils.h new file mode 100644 index 0000000..90f3945 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Utils.h @@ -0,0 +1,124 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ObTypes.h" + +/** + * @brief Transform a 3d point of a source coordinate system into a 3d point of the target coordinate system. + * + * @param[in] source_point3f Source 3d point value + * @param[in] extrinsic Transformation matrix from source to target + * @param[out] target_point3f Target 3d point value + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return bool Transform result + */ +OB_EXPORT bool ob_transformation_3d_to_3d(const OBPoint3f source_point3f, OBExtrinsic extrinsic, OBPoint3f *target_point3f, ob_error **error); + +/** + * @brief Transform a 2d pixel coordinate with an associated depth value of the source camera into a 3d point of the target coordinate system. + * + * @param[in] source_point2f Source 2d point value + * @param[in] source_depth_pixel_value The depth of sourcePoint2f in millimeters + * @param[in] source_intrinsic Source intrinsic parameters + * @param[in] extrinsic Transformation matrix from source to target + * @param[out] target_point3f Target 3d point value + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return bool Transform result + */ +OB_EXPORT bool ob_transformation_2d_to_3d(const OBPoint2f source_point2f, const float source_depth_pixel_value, const OBCameraIntrinsic source_intrinsic, + OBExtrinsic extrinsic, OBPoint3f *target_point3f, ob_error **error); + +/** + * @brief Transform a 3d point of a source coordinate system into a 2d pixel coordinate of the target camera. + * + * @param[in] source_point3f Source 3d point value + * @param[in] target_intrinsic Target intrinsic parameters + * @param[in] target_distortion Target distortion parameters + * @param[in] extrinsic Transformation matrix from source to target + * @param[out] target_point2f Target 2d point value + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return bool Transform result + */ +OB_EXPORT bool ob_transformation_3d_to_2d(const OBPoint3f source_point3f, const OBCameraIntrinsic target_intrinsic, const OBCameraDistortion target_distortion, + OBExtrinsic extrinsic, OBPoint2f *target_point2f, ob_error **error); + +/** + * @brief Transform a 2d pixel coordinate with an associated depth value of the source camera into a 2d pixel coordinate of the target camera + * + * @param[in] source_intrinsic Source intrinsic parameters + * @param[in] source_distortion Source distortion parameters + * @param[in] source_point2f Source 2d point value + * @param[in] source_depth_pixel_value The depth of sourcePoint2f in millimeters + * @param[in] target_intrinsic Target intrinsic parameters + * @param[in] target_distortion Target distortion parameters + * @param[in] extrinsic Transformation matrix from source to target + * @param[out] target_point2f Target 2d point value + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return bool Transform result + */ +OB_EXPORT bool ob_transformation_2d_to_2d(const OBPoint2f source_point2f, const float source_depth_pixel_value, const OBCameraIntrinsic source_intrinsic, + const OBCameraDistortion source_distortion, const OBCameraIntrinsic target_intrinsic, + const OBCameraDistortion target_distortion, OBExtrinsic extrinsic, OBPoint2f *target_point2f, ob_error **error); + +// \deprecated This function is deprecated and will be removed in a future version. +OB_EXPORT ob_frame *transformation_depth_frame_to_color_camera(ob_device *device, ob_frame *depth_frame, uint32_t target_color_camera_width, + uint32_t target_color_camera_height, ob_error **error); + +// \deprecated This function is deprecated and will be removed in a future version. +OB_EXPORT bool transformation_init_xy_tables(const ob_calibration_param calibration_param, const ob_sensor_type sensor_type, float *data, uint32_t *data_size, + ob_xy_tables *xy_tables, ob_error **error); + +// \deprecated This function is deprecated and will be removed in a future version. +OB_EXPORT void transformation_depth_to_pointcloud(ob_xy_tables *xy_tables, const void *depth_image_data, void *pointcloud_data, ob_error **error); + +// \deprecated This function is deprecated and will be removed in a future version. +OB_EXPORT void transformation_depth_to_rgbd_pointcloud(ob_xy_tables *xy_tables, const void *depth_image_data, const void *color_image_data, + void *pointcloud_data, ob_error **error); + +// \deprecated This function is deprecated and will be removed in a future version. +// Use the ob_transformation_3d_to_3d instead. +OB_EXPORT bool ob_calibration_3d_to_3d(const ob_calibration_param calibration_param, const ob_point3f source_point3f, const ob_sensor_type source_sensor_type, + const ob_sensor_type target_sensor_type, ob_point3f *target_point3f, ob_error **error); + +// \deprecated This function is deprecated and will be removed in a future version. +// Use the ob_transformation_2d_to_3d instead. +OB_EXPORT bool ob_calibration_2d_to_3d(const ob_calibration_param calibration_param, const ob_point2f source_point2f, const float source_depth_pixel_value, + const ob_sensor_type source_sensor_type, const ob_sensor_type target_sensor_type, ob_point3f *target_point3f, + ob_error **error); + +// \deprecated This function is deprecated and will be removed in a future version. +// Use the ob_transformation_3d_to_2d instead. +OB_EXPORT bool ob_calibration_3d_to_2d(const ob_calibration_param calibration_param, const ob_point3f source_point3f, const ob_sensor_type source_sensor_type, + const ob_sensor_type target_sensor_type, ob_point2f *target_point2f, ob_error **error); + +// \deprecated This function is deprecated and will be removed in a future version. +// Use the ob_transformation_2d_to_2d instead. +OB_EXPORT bool ob_calibration_2d_to_2d(const ob_calibration_param calibration_param, const ob_point2f source_point2f, const float source_depth_pixel_value, + const ob_sensor_type source_sensor_type, const ob_sensor_type target_sensor_type, ob_point2f *target_point2f, + ob_error **error); +/** + * @brief save point cloud to ply file. + * + * @param[in] file_name Point cloud save path + * @param[in] frame Point cloud frame + * @param[in] save_binary Binary or textual,true: binary, false: textual + * @param[in] use_mesh Save mesh or not, true: save as mesh, false: not save as mesh + * @param[in] mesh_threshold Distance threshold for creating faces in point cloud,default value :50 + * @param[out] error Pointer to an error object that will be set if an error occurs. + * + * @return bool save point cloud result + */ +OB_EXPORT bool ob_save_pointcloud_to_ply(const char *file_name, ob_frame *frame, bool save_binary, bool use_mesh, float mesh_threshold, ob_error **error); +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Version.h b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Version.h new file mode 100644 index 0000000..874e7df --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/h/Version.h @@ -0,0 +1,55 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Version.h + * @brief Functions for retrieving the SDK version number information. + * + */ +#pragma once + +#include "Export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get the SDK version number. + * + * @return int The SDK version number. + */ +OB_EXPORT int ob_get_version(void); + +/** + * @brief Get the SDK major version number. + * + * @return int The SDK major version number. + */ +OB_EXPORT int ob_get_major_version(void); + +/** + * @brief Get the SDK minor version number. + * + * @return int The SDK minor version number. + */ +OB_EXPORT int ob_get_minor_version(void); + +/** + * @brief Get the SDK patch version number. + * + * @return int The SDK patch version number. + */ +OB_EXPORT int ob_get_patch_version(void); + +/** + * @brief Get the SDK stage version. + * @attention The returned char* does not need to be freed. + * + * @return const char* The SDK stage version. + */ +OB_EXPORT const char *ob_get_stage_version(void); + +#ifdef __cplusplus +} +#endif diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Context.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Context.hpp new file mode 100644 index 0000000..6bf3392 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Context.hpp @@ -0,0 +1,269 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Context.hpp + * @brief The SDK context class, which serves as the entry point to the underlying SDK. It is used to query device lists, handle device callbacks, and set the + * log level. + * + */ +#pragma once + +#include "libobsensor/h/Context.h" +#include "Types.hpp" +#include "Error.hpp" + +#include +#include + +namespace ob { + +// forward declarations +class Device; +class DeviceInfo; +class DeviceList; + +class Context { +public: + /** + * @brief Type definition for the device changed callback function. + * + * @param removedList The list of removed devices. + * @param addedList The list of added devices. + */ + typedef std::function removedList, std::shared_ptr addedList)> DeviceChangedCallback; + + /** + * @brief Type definition for the log output callback function. + * + * @param severity The current callback log level. + * @param logMsg The log message. + */ + typedef std::function LogCallback; + +private: + ob_context *impl_ = nullptr; + DeviceChangedCallback deviceChangedCallback_; + // static LogCallback logCallback_; + +public: + /** + * @brief Context constructor. + * @brief The Context class is a management class that describes the runtime of the SDK. It is responsible for applying and releasing resources for the SDK. + * The context has the ability to manage multiple devices, enumerate devices, monitor device callbacks, and enable functions such as multi-device + * synchronization. + * + * @param[in] configPath The path to the configuration file. If the path is empty, the default configuration will be used. + */ + explicit Context(const char *configPath = "") { + ob_error *error = nullptr; + impl_ = ob_create_context_with_config(configPath, &error); + Error::handle(&error); + } + + /** + * @brief Context destructor. + */ + ~Context() noexcept { + ob_error *error = nullptr; + ob_delete_context(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Queries the enumerated device list. + * + * @return std::shared_ptr A pointer to the device list class. + */ + std::shared_ptr queryDeviceList() const { + ob_error *error = nullptr; + auto list = ob_query_device_list(impl_, &error); + Error::handle(&error); + return std::make_shared(list); + } + + /** + * @brief enable or disable net device enumeration. + * @brief after enable, the net device will be discovered automatically and can be retrieved by @ref queryDeviceList. The default state can be set in the + * configuration file. + * + * @attention Net device enumeration by gvcp protocol, if the device is not in the same subnet as the host, it will be discovered but cannot be connected. + * + * @param[in] enable true to enable, false to disable + */ + void enableNetDeviceEnumeration(bool enable) const { + ob_error *error = nullptr; + ob_enable_net_device_enumeration(impl_, enable, &error); + Error::handle(&error); + } + + /** + * @brief "Force" a static IP address configuration in a device identified by its MAC Address. + * + * @param[in] macAddress MAC address of the network device. + * You can obtain it from @ref DeviceList::uid(), or specify it manually + * in the format xx:xx:xx:xx:xx:xx, where each xx is a two-digit hexadecimal value. + * @param[in] config The new IP configuration. + * @return bool true if the configuration command was processed successfully, false otherwise. + * + * @note This applies to all Orbbec GigE Vision devices + */ + bool forceIp(const char *macAddress, const OBNetIpConfig &config) { + ob_error *error = nullptr; + auto res = ob_force_ip_config(macAddress, config, &error); + Error::handle(&error); + return res; + } + + /** + * @brief Creates a network device with the specified IP address and port. + * + * @param[in] address The IP address, ipv4 only. such as "192.168.1.10" + * @param[in] port The port number, currently only support 8090 + * @return std::shared_ptr The created device object. + */ + std::shared_ptr createNetDevice(const char *address, uint16_t port) const { + ob_error *error = nullptr; + auto device = ob_create_net_device(impl_, address, port, &error); + Error::handle(&error); + return std::make_shared(device); + } + + /** + * @brief Set the device plug-in callback function. + * @attention This function supports multiple callbacks. Each call to this function adds a new callback to an internal list. + * + * @param callback The function triggered when the device is plugged and unplugged. + */ + void setDeviceChangedCallback(DeviceChangedCallback callback) { + deviceChangedCallback_ = callback; + ob_error *error = nullptr; + ob_set_device_changed_callback(impl_, &Context::deviceChangedCallback, this, &error); + Error::handle(&error); + } + + /** + * @brief Activates device clock synchronization to synchronize the clock of the host and all created devices (if supported). + * + * @param repeatIntervalMsec The interval for auto-repeated synchronization, in milliseconds. If the value is 0, synchronization is performed only once. + */ + void enableDeviceClockSync(uint64_t repeatIntervalMsec) const { + ob_error *error = nullptr; + ob_enable_device_clock_sync(impl_, repeatIntervalMsec, &error); + Error::handle(&error); + } + + /** + * @brief Frees idle memory from the internal frame memory pool. + * @brief The SDK includes an internal frame memory pool that caches memory allocated for frames received from devices. + */ + void freeIdleMemory() const { + ob_error *error = nullptr; + ob_free_idle_memory(impl_, &error); + Error::handle(&error); + } + + /** + * @brief For linux, there are two ways to enable the UVC backend: libuvc and v4l2. This function is used to set the backend type. + * @brief It is effective when the new device is created. + * + * @attention This interface is only available for Linux. + * + * @param[in] type The backend type to be used. + */ + void setUvcBackendType(OBUvcBackendType type) const { + ob_error *error = nullptr; + ob_set_uvc_backend_type(impl_, type, &error); + Error::handle(&error); + } + + /** + * @brief Set the level of the global log, which affects both the log level output to the console, output to the file and output the user defined + * callback. + * + * @param severity The log output level. + */ + static void setLoggerSeverity(OBLogSeverity severity) { + ob_error *error = nullptr; + ob_set_logger_severity(severity, &error); + Error::handle(&error); + } + + /** + * @brief Set log output to a file. + * + * @param severity The log level output to the file. + * @param directory The log file output path. If the path is empty, the existing settings will continue to be used (if the existing configuration is also + * empty, the log will not be output to the file). + */ + static void setLoggerToFile(OBLogSeverity severity, const char *directory) { + ob_error *error = nullptr; + ob_set_logger_to_file(severity, directory, &error); + Error::handle(&error); + } + + /** + * @brief Set log output to the console. + * + * @param severity The log level output to the console. + */ + static void setLoggerToConsole(OBLogSeverity severity) { + ob_error *error = nullptr; + ob_set_logger_to_console(severity, &error); + Error::handle(&error); + } + + /** + * @brief Set the logger to callback. + * + * @param severity The callback log level. + * @param callback The callback function. + */ + static void setLoggerToCallback(OBLogSeverity severity, LogCallback callback) { + ob_error *error = nullptr; + Context::getLogCallback() = callback; + ob_set_logger_to_callback(severity, &Context::logCallback, &Context::getLogCallback(), &error); + Error::handle(&error); + } + + /** + * @brief Set the extensions directory + * @brief The extensions directory is used to search for dynamic libraries that provide additional functionality to the SDK, such as the Frame filters. + * + * @attention Should be called before creating the context and pipeline, otherwise the default extensions directory (./extensions) will be used. + * + * @param directory Path to the extensions directory. If the path is empty, the existing settings will continue to be used (if the existing + */ + static void setExtensionsDirectory(const char *directory) { + ob_error *error = nullptr; + ob_set_extensions_directory(directory, &error); + Error::handle(&error); + } + +private: + static void deviceChangedCallback(ob_device_list *removedList, ob_device_list *addedList, void *userData) { + auto ctx = static_cast(userData); + if(ctx && ctx->deviceChangedCallback_) { + auto removed = std::make_shared(removedList); + auto added = std::make_shared(addedList); + ctx->deviceChangedCallback_(removed, added); + } + } + + static void logCallback(OBLogSeverity severity, const char *logMsg, void *userData) { + auto cb = static_cast(userData); + if(cb) { + (*cb)(severity, logMsg); + } + } + + // Lazy initialization of the logcallback_. The purpose is to initialize logcallback_ in .hpp + static LogCallback &getLogCallback() { + static LogCallback logCallback_ = nullptr; + return logCallback_; + } + +// for backward compatibility +#define enableMultiDeviceSync enableDeviceClockSync +}; +} // namespace ob diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Device.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Device.hpp new file mode 100644 index 0000000..a026d53 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Device.hpp @@ -0,0 +1,1655 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Device.hpp + * @brief Device related types, including operations such as getting and creating a device, setting and obtaining device attributes, and obtaining sensors + * + */ +#pragma once +#include "Types.hpp" + +#include "libobsensor/h/Property.h" +#include "libobsensor/h/Device.h" +#include "libobsensor/h/Advanced.h" +#include "libobsensor/hpp/Filter.hpp" +#include "libobsensor/hpp/Sensor.hpp" + +#include "Error.hpp" +#include +#include +#include +#include +#include + +namespace ob { + +class DeviceInfo; +class SensorList; +class DevicePresetList; +class OBDepthWorkModeList; +class CameraParamList; +class DeviceFrameInterleaveList; +class PresetResolutionConfigList; + +class Device { +public: + /** + * @brief Callback function for device firmware update progress + * + * @param state The device firmware update status. + * @param message Status information. + * @param percent The percentage of the update progress. + */ + typedef std::function DeviceFwUpdateCallback; + + /** + * @brief Callback function for device status updates. + * + * @param state The device status. + * @param message Status information. + */ + typedef std::function DeviceStateChangedCallback; + +protected: + ob_device *impl_ = nullptr; + DeviceStateChangedCallback deviceStateChangeCallback_; + DeviceFwUpdateCallback fwUpdateCallback_; + +public: + /** + * @brief Describe the entity of the RGBD camera, representing a specific model of RGBD camera + */ + explicit Device(ob_device_t *impl) : impl_(impl) {} + + Device(Device &&other) noexcept : impl_(other.impl_) { + other.impl_ = nullptr; + } + + Device &operator=(Device &&other) noexcept { + if(this != &other) { + ob_error *error = nullptr; + ob_delete_device(impl_, &error); + Error::handle(&error); + impl_ = other.impl_; + other.impl_ = nullptr; + } + return *this; + } + + Device(const Device &) = delete; + Device &operator=(const Device &) = delete; + + virtual ~Device() noexcept { + ob_error *error = nullptr; + ob_delete_device(impl_, &error); + Error::handle(&error, false); + } + + ob_device_t *getImpl() const { + return impl_; + } + + /** + * @brief Get device information + * + * @return std::shared_ptr return device information + */ + std::shared_ptr getDeviceInfo() const { + ob_error *error = nullptr; + auto info = ob_device_get_device_info(impl_, &error); + Error::handle(&error); + return std::make_shared(info); + } + + /** + * @brief Check if the extension information is exist + * + * @param infoKey The key of the extension information + * @return bool Whether the extension information exists + */ + bool isExtensionInfoExist(const std::string &infoKey) const { + ob_error *error = nullptr; + auto exist = ob_device_is_extension_info_exist(impl_, infoKey.c_str(), &error); + Error::handle(&error); + return exist; + } + + /** + * @brief Get information about extensions obtained from SDK supported by the device + * + * @param infoKey The key of the extension information + * @return const char* Returns extended information about the device + */ + const char *getExtensionInfo(const std::string &infoKey) const { + ob_error *error = nullptr; + const char *info = ob_device_get_extension_info(impl_, infoKey.c_str(), &error); + Error::handle(&error); + return info; + } + + /** + * @brief Get device sensor list + * + * @return std::shared_ptr return the sensor list + */ + std::shared_ptr getSensorList() const { + ob_error *error = nullptr; + auto list = ob_device_get_sensor_list(impl_, &error); + Error::handle(&error); + return std::make_shared(list); + } + + /** + * @brief Get specific type of sensor + * if device not open, SDK will automatically open the connected device and return to the instance + * + * @return std::shared_ptr return the sensor example, if the device does not have the device,return nullptr + */ + std::shared_ptr getSensor(OBSensorType type) const { + ob_error *error = nullptr; + auto sensor = ob_device_get_sensor(impl_, type, &error); + Error::handle(&error); + return std::make_shared(sensor); + } + + /** + * @brief Set int type of device property + * + * @param propertyId Property id + * @param value Property value to be set + */ + void setIntProperty(OBPropertyID propertyId, int32_t value) const { + ob_error *error = nullptr; + ob_device_set_int_property(impl_, propertyId, value, &error); + Error::handle(&error); + } + + /** + * @brief Set float type of device property + * + * @param propertyId Property id + * @param value Property value to be set + */ + void setFloatProperty(OBPropertyID propertyId, float value) const { + ob_error *error = nullptr; + ob_device_set_float_property(impl_, propertyId, value, &error); + Error::handle(&error); + } + + /** + * @brief Set bool type of device property + * + * @param propertyId Property id + * @param value Property value to be set + */ + void setBoolProperty(OBPropertyID propertyId, bool value) const { + ob_error *error = nullptr; + ob_device_set_bool_property(impl_, propertyId, value, &error); + Error::handle(&error); + } + + /** + * @brief Get int type of device property + * + * @param propertyId Property id + * @return int32_t Property to get + */ + int32_t getIntProperty(OBPropertyID propertyId) const { + ob_error *error = nullptr; + auto value = ob_device_get_int_property(impl_, propertyId, &error); + Error::handle(&error); + return value; + } + + /** + * @brief Get float type of device property + * + * @param propertyId Property id + * @return float Property to get + */ + float getFloatProperty(OBPropertyID propertyId) const { + ob_error *error = nullptr; + auto value = ob_device_get_float_property(impl_, propertyId, &error); + Error::handle(&error); + return value; + } + + /** + * @brief Get bool type of device property + * + * @param propertyId Property id + * @return bool Property to get + */ + bool getBoolProperty(OBPropertyID propertyId) const { + ob_error *error = nullptr; + auto value = ob_device_get_bool_property(impl_, propertyId, &error); + Error::handle(&error); + return value; + } + + /** + * @brief Get int type device property range (including current value and default value) + * + * @param propertyId Property id + * @return OBIntPropertyRange Property range + */ + OBIntPropertyRange getIntPropertyRange(OBPropertyID propertyId) const { + ob_error *error = nullptr; + auto range = ob_device_get_int_property_range(impl_, propertyId, &error); + Error::handle(&error); + return range; + } + + /** + * @brief Get float type device property range((including current value and default value) + * + * @param propertyId Property id + * @return OBFloatPropertyRange Property range + */ + OBFloatPropertyRange getFloatPropertyRange(OBPropertyID propertyId) const { + ob_error *error = nullptr; + auto range = ob_device_get_float_property_range(impl_, propertyId, &error); + Error::handle(&error); + return range; + } + + /** + * @brief Get bool type device property range (including current value and default value) + * + * @param propertyId The ID of the property + * @return OBBoolPropertyRange The range of the property + */ + OBBoolPropertyRange getBoolPropertyRange(OBPropertyID propertyId) const { + ob_error *error = nullptr; + auto range = ob_device_get_bool_property_range(impl_, propertyId, &error); + Error::handle(&error); + return range; + } + + /** + * @brief Set the structured data type of a device property + * + * @param propertyId The ID of the property + * @param data The data to set + * @param dataSize The size of the data to set + */ + void setStructuredData(OBPropertyID propertyId, const uint8_t *data, uint32_t dataSize) const { + ob_error *error = nullptr; + ob_device_set_structured_data(impl_, propertyId, data, dataSize, &error); + Error::handle(&error); + } + + /** + * @brief Get the structured data type of a device property + * + * @param propertyId The ID of the property + * @param data The property data obtained + * @param dataSize The size of the data obtained + */ + void getStructuredData(OBPropertyID propertyId, uint8_t *data, uint32_t *dataSize) const { + ob_error *error = nullptr; + ob_device_get_structured_data(impl_, propertyId, data, dataSize, &error); + Error::handle(&error); + } + + /** + * @brief Set the customer data type of a device property + * + * @param data The data to set + * @param dataSize The size of the data to set,the maximum length cannot exceed 65532 bytes. + */ + void writeCustomerData(const void *data, uint32_t dataSize) { + ob_error *error = nullptr; + ob_device_write_customer_data(impl_, data, dataSize, &error); + Error::handle(&error); + } + + /** + * @brief Get the customer data type of a device property + * + * @param data The property data obtained + * @param dataSize The size of the data obtained + */ + void readCustomerData(void *data, uint32_t *dataSize) { + ob_error *error = nullptr; + ob_device_read_customer_data(impl_, data, dataSize, &error); + Error::handle(&error); + } + + /** + * @brief Get the number of properties supported by the device + * + * @return The number of supported properties + */ + int getSupportedPropertyCount() const { + ob_error *error = nullptr; + auto count = ob_device_get_supported_property_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Get the supported properties of the device + * + * @param index The index of the property + * @return The type of supported property + */ + OBPropertyItem getSupportedProperty(uint32_t index) const { + ob_error *error = nullptr; + auto item = ob_device_get_supported_property_item(impl_, index, &error); + Error::handle(&error); + return item; + } + + /** + * @brief Check if a property permission is supported + * + * @param propertyId The ID of the property + * @param permission The read and write permissions to check + * @return Whether the property permission is supported + */ + bool isPropertySupported(OBPropertyID propertyId, OBPermissionType permission) const { + ob_error *error = nullptr; + auto result = ob_device_is_property_supported(impl_, propertyId, permission, &error); + Error::handle(&error); + return result; + } + + /** + * @brief Check if the global timestamp is supported for the device + * + * @return Whether the global timestamp is supported + */ + bool isGlobalTimestampSupported() const { + ob_error *error = nullptr; + auto result = ob_device_is_global_timestamp_supported(impl_, &error); + Error::handle(&error); + return result; + } + + /** + * @brief Enable or disable the global timestamp + * + * @param enable Whether to enable the global timestamp + */ + void enableGlobalTimestamp(bool enable) { + ob_error *error = nullptr; + ob_device_enable_global_timestamp(impl_, enable, &error); + Error::handle(&error); + } + + /** + * @brief Update the device firmware + * + * @param filePath Firmware path + * @param callback Firmware Update progress and status callback + * @param async Whether to execute asynchronously + */ + void updateFirmware(const char *filePath, DeviceFwUpdateCallback callback, bool async = true) { + ob_error *error = nullptr; + fwUpdateCallback_ = callback; + ob_device_update_firmware(impl_, filePath, &Device::firmwareUpdateCallback, async, this, &error); + Error::handle(&error); + } + + /** + * @brief Update the device firmware from data + * + * @param firmwareData Firmware data + * @param firmwareDataSize Firmware data size + * @param callback Firmware Update progress and status callback + * @param async Whether to execute asynchronously + */ + void updateFirmwareFromData(const uint8_t *firmwareData, uint32_t firmwareDataSize, DeviceFwUpdateCallback callback, bool async = true) { + ob_error *error = nullptr; + fwUpdateCallback_ = callback; + ob_device_update_firmware_from_data(impl_, firmwareData, firmwareDataSize, &Device::firmwareUpdateCallback, async, this, &error); + Error::handle(&error); + } + + /** + * @brief Update the device optional depth presets + * + * @param filePathList A list(2D array) of preset file paths, each up to OB_PATH_MAX characters. + * @param pathCount The number of the preset file paths. + * @param callback Preset update progress and status callback + */ + void updateOptionalDepthPresets(const char filePathList[][OB_PATH_MAX], uint8_t pathCount, DeviceFwUpdateCallback callback) { + ob_error *error = nullptr; + fwUpdateCallback_ = callback; + ob_device_update_optional_depth_presets(impl_, filePathList, pathCount, &Device::firmwareUpdateCallback, this, &error); + Error::handle(&error); + } + + /** + * @brief Set the device state changed callbacks + * + * @param callback The callback function that is triggered when the device status changes (for example, the frame rate is automatically reduced or the + * stream is closed due to high temperature, etc.) + */ + void setDeviceStateChangedCallback(DeviceStateChangedCallback callback) { + ob_error *error = nullptr; + deviceStateChangeCallback_ = callback; + ob_device_set_state_changed_callback(impl_, &Device::deviceStateChangedCallback, this, &error); + Error::handle(&error); + } + + static void deviceStateChangedCallback(OBDeviceState state, const char *message, void *userData) { + auto device = static_cast(userData); + device->deviceStateChangeCallback_(state, message); + } + + /** + * @brief Get current depth work mode + * + * @return ob_depth_work_mode Current depth work mode + */ + OBDepthWorkMode getCurrentDepthWorkMode() const { + ob_error *error = nullptr; + auto mode = ob_device_get_current_depth_work_mode(impl_, &error); + Error::handle(&error); + return mode; + } + + /** + * @brief Get current depth mode name + * @brief According the current preset name to return current depth mode name + * @return const char* return the current depth mode name. + */ + const char *getCurrentDepthModeName() { + ob_error *error = nullptr; + auto name = ob_device_get_current_depth_work_mode_name(impl_, &error); + Error::handle(&error); + return name; + } + + /** + * @brief Switch depth work mode by OBDepthWorkMode. Prefer invoke switchDepthWorkMode(const char *modeName) to switch depth mode + * when known the complete name of depth work mode. + * @param[in] workMode Depth work mode come from ob_depth_work_mode_list which return by ob_device_get_depth_work_mode_list + */ + OBStatus switchDepthWorkMode(const OBDepthWorkMode &workMode) const { + ob_error *error = nullptr; + auto status = ob_device_switch_depth_work_mode(impl_, &workMode, &error); + Error::handle(&error); + return status; + } + + /** + * @brief Switch depth work mode by work mode name. + * + * @param[in] modeName Depth work mode name which equals to OBDepthWorkMode.name + */ + OBStatus switchDepthWorkMode(const char *modeName) const { + ob_error *error = nullptr; + auto status = ob_device_switch_depth_work_mode_by_name(impl_, modeName, &error); + Error::handle(&error); + return status; + } + + /** + * @brief Request support depth work mode list + * @return OBDepthWorkModeList list of ob_depth_work_mode + */ + std::shared_ptr getDepthWorkModeList() const { + ob_error *error = nullptr; + auto list = ob_device_get_depth_work_mode_list(impl_, &error); + Error::handle(&error); + return std::make_shared(list); + } + + /** + * @brief Device restart + * @attention The device will be disconnected and reconnected. After the device is disconnected, the access to the Device object interface may be abnormal. + * Please delete the object directly and obtain it again after the device is reconnected. + */ + void reboot() const { + ob_error *error = nullptr; + ob_device_reboot(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Device restart delay mode + * @attention The device will be disconnected and reconnected. After the device is disconnected, the access to the Device object interface may be abnormal. + * Please delete the object directly and obtain it again after the device is reconnected. + * Support devices: Gemini2 L + * + * @param[in] delayMs Time unit: ms. delayMs == 0: No delay; delayMs > 0, Delay millisecond connect to host device after reboot + */ + void reboot(uint32_t delayMs) const { + setIntProperty(OB_PROP_DEVICE_REBOOT_DELAY_INT, delayMs); + reboot(); + } + + /** + * @brief Enable or disable the device heartbeat. + * @brief After enable the device heartbeat, the sdk will start a thread to send heartbeat signal to the device error every 3 seconds. + * + * @attention If the device does not receive the heartbeat signal for a long time, it will be disconnected and rebooted. + * + * @param[in] enable Whether to enable the device heartbeat. + */ + void enableHeartbeat(bool enable) const { + ob_error *error = nullptr; + ob_device_enable_heartbeat(impl_, enable, &error); + Error::handle(&error); + } + + /** + * @brief Get the supported multi device sync mode bitmap of the device. + * @brief For example, if the return value is 0b00001100, it means the device supports @ref OB_MULTI_DEVICE_SYNC_MODE_PRIMARY and @ref + * OB_MULTI_DEVICE_SYNC_MODE_SECONDARY. User can check the supported mode by the code: + * ```c + * if(supported_mode_bitmap & OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN){ + * //support OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN + * } + * if(supported_mode_bitmap & OB_MULTI_DEVICE_SYNC_MODE_STANDALONE){ + * //support OB_MULTI_DEVICE_SYNC_MODE_STANDALONE + * } + * // and so on + * ``` + * @return uint16_t return the supported multi device sync mode bitmap of the device. + */ + uint16_t getSupportedMultiDeviceSyncModeBitmap() const { + ob_error *error = nullptr; + auto mode = ob_device_get_supported_multi_device_sync_mode_bitmap(impl_, &error); + Error::handle(&error); + return mode; + } + + /** + * @brief set the multi device sync configuration of the device. + * + * @param[in] config The multi device sync configuration. + */ + void setMultiDeviceSyncConfig(const OBMultiDeviceSyncConfig &config) const { + ob_error *error = nullptr; + ob_device_set_multi_device_sync_config(impl_, &config, &error); + Error::handle(&error); + } + + /** + * @brief get the multi device sync configuration of the device. + * + * @return OBMultiDeviceSyncConfig return the multi device sync configuration of the device. + */ + OBMultiDeviceSyncConfig getMultiDeviceSyncConfig() const { + ob_error *error = nullptr; + auto config = ob_device_get_multi_device_sync_config(impl_, &error); + Error::handle(&error); + return config; + } + + /** + * @brief send the capture command to the device. + * @brief The device will start one time image capture after receiving the capture command when it is in the @ref + * OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING + * + * @attention The frequency of the user call this function multiplied by the number of frames per trigger should be less than the frame rate of the stream. + * The number of frames per trigger can be set by @ref framesPerTrigger. + * @attention For some models, receive and execute the capture command will have a certain delay and performance consumption, so the frequency of calling + * this function should not be too high, please refer to the product manual for the specific supported frequency. + * @attention If the device is not in the @ref OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING mode, device will ignore the capture command. + */ + void triggerCapture() const { + ob_error *error = nullptr; + ob_device_trigger_capture(impl_, &error); + Error::handle(&error); + } + + /** + * @brief set the timestamp reset configuration of the device. + */ + void setTimestampResetConfig(const OBDeviceTimestampResetConfig &config) const { + ob_error *error = nullptr; + ob_device_set_timestamp_reset_config(impl_, &config, &error); + Error::handle(&error); + } + + /** + * @brief get the timestamp reset configuration of the device. + * + * @return OBDeviceTimestampResetConfig return the timestamp reset configuration of the device. + */ + OBDeviceTimestampResetConfig getTimestampResetConfig() const { + ob_error *error = nullptr; + auto config = ob_device_get_timestamp_reset_config(impl_, &error); + Error::handle(&error); + return config; + } + + /** + * @brief send the timestamp reset command to the device. + * @brief The device will reset the timer for calculating the timestamp for output frames to 0 after receiving the timestamp reset command when the + * timestamp reset function is enabled. The timestamp reset function can be enabled by call @ref ob_device_set_timestamp_reset_config. + * @brief Before calling this function, user should call @ref ob_device_set_timestamp_reset_config to disable the timestamp reset function (It is not + * required for some models, but it is still recommended to do so for code compatibility). + * + * @attention If the stream of the device is started, the timestamp of the continuous frames output by the stream will jump once after the timestamp reset. + * @attention Due to the timer of device is not high-accuracy, the timestamp of the continuous frames output by the stream will drift after a long time. + * User can call this function periodically to reset the timer to avoid the timestamp drift, the recommended interval time is 60 minutes. + */ + void timestampReset() const { + ob_error *error = nullptr; + ob_device_timestamp_reset(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Alias for @ref timestampReset since it is more accurate. + */ +#define timerReset timestampReset + + /** + * @brief synchronize the timer of the device with the host. + * @brief After calling this function, the timer of the device will be synchronized with the host. User can call this function to multiple devices to + * synchronize all timers of the devices. + * + * @attention If the stream of the device is started, the timestamp of the continuous frames output by the stream will may jump once after the timer + * sync. + * @attention Due to the timer of device is not high-accuracy, the timestamp of the continuous frames output by the stream will drift after a long time. + * User can call this function periodically to synchronize the timer to avoid the timestamp drift, the recommended interval time is 60 minutes. + * + */ + void timerSyncWithHost() const { + ob_error *error = nullptr; + ob_device_timer_sync_with_host(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Get current preset name + * @brief The preset mean a set of parameters or configurations that can be applied to the device to achieve a specific effect or function. + * @return const char* return the current preset name, it should be one of the preset names returned by @ref getAvailablePresetList. + */ + const char *getCurrentPresetName() const { + ob_error *error = nullptr; + const char *name = ob_device_get_current_preset_name(impl_, &error); + Error::handle(&error); + return name; + } + + /** + * @brief load the preset according to the preset name. + * @attention After loading the preset, the settings in the preset will set to the device immediately. Therefore, it is recommended to re-read the device + * settings to update the user program temporarily. + * @param presetName The preset name to set. The name should be one of the preset names returned by @ref getAvailablePresetList. + */ + void loadPreset(const char *presetName) const { + ob_error *error = nullptr; + ob_device_load_preset(impl_, presetName, &error); + Error::handle(&error); + } + + /** + * @brief Get available preset list + * @brief The available preset list usually defined by the device manufacturer and restores on the device. + * @brief User can load the custom preset by calling @ref loadPresetFromJsonFile to append the available preset list. + * + * @return DevicePresetList return the available preset list. + */ + std::shared_ptr getAvailablePresetList() const { + ob_error *error = nullptr; + auto list = ob_device_get_available_preset_list(impl_, &error); + Error::handle(&error); + return std::make_shared(list); + } + + /** + * @brief Load custom preset from file. + * @brief After loading the custom preset, the settings in the custom preset will set to the device immediately. + * @brief After loading the custom preset, the available preset list will be appended with the custom preset and named as the file name. + * + * @attention The user should ensure that the custom preset file is adapted to the device and the settings in the file are valid. + * @attention It is recommended to re-read the device settings to update the user program temporarily after successfully loading the custom preset. + * + * @param filePath The path of the custom preset file. + */ + void loadPresetFromJsonFile(const char *filePath) const { + ob_error *error = nullptr; + ob_device_load_preset_from_json_file(impl_, filePath, &error); + Error::handle(&error); + } + + /** + * @brief Load custom preset from data. + * @brief After loading the custom preset, the settings in the custom preset will set to the device immediately. + * @brief After loading the custom preset, the available preset list will be appended with the custom preset and named as the @ref presetName. + * + * @attention The user should ensure that the custom preset data is adapted to the device and the settings in the data are valid. + * @attention It is recommended to re-read the device settings to update the user program temporarily after successfully loading the custom preset. + * + * @param data The custom preset data. + * @param size The size of the custom preset data. + */ + void loadPresetFromJsonData(const char *presetName, const uint8_t *data, uint32_t size) { + ob_error *error = nullptr; + ob_device_load_preset_from_json_data(impl_, presetName, data, size, &error); + } + + /** + * @brief Export current device settings as a preset json data. + * @brief After exporting the preset, a new preset named as the @ref presetName will be added to the available preset list. + * + * @attention The memory of the data is allocated by the SDK, and will automatically be released by the SDK. + * @attention The memory of the data will be reused by the SDK on the next call, so the user should copy the data to a new buffer if it needs to be + * preserved. + * + * @param[out] data return the preset json data. + * @param[out] dataSize return the size of the preset json data. + */ + void exportSettingsAsPresetJsonData(const char *presetName, const uint8_t **data, uint32_t *dataSize) { + ob_error *error = nullptr; + ob_device_export_current_settings_as_preset_json_data(impl_, presetName, data, dataSize, &error); + } + + /** + * @brief Export current device settings as a preset json file. + * @brief The exported preset file can be loaded by calling @ref loadPresetFromJsonFile to restore the device setting. + * @brief After exporting the preset, a new preset named as the @ref filePath will be added to the available preset list. + * + * @param filePath The path of the preset file to be exported. + */ + void exportSettingsAsPresetJsonFile(const char *filePath) const { + ob_error *error = nullptr; + ob_device_export_current_settings_as_preset_json_file(impl_, filePath, &error); + Error::handle(&error); + } + + /** + * @brief Get the current device status. + * + * @return OBDeviceState The device state information. + */ + OBDeviceState getDeviceState() { + OBDeviceState state = {}; + ob_error *error = nullptr; + state = ob_device_get_device_state(impl_, &error); + return state; + } + + /** + * @brief Send data to the device and receive data from the device. + * @brief This is a factory and debug function, which can be used to send and receive data from the device. The data format is secret and belongs to the + * device vendor. + * + * @attention The send and receive data buffer are managed by the caller, the receive data buffer should be allocated at 1024 bytes or larger. + * + * @param[in] sendData The data to be sent to the device. + * @param[in] sendDataSize The size of the data to be sent to the device. + * @param[out] receiveData The data received from the device. + * @param[in,out] receiveDataSize The requeseted size of the data received from the device, and the actual size of the data received from the device. + */ + void sendAndReceiveData(const uint8_t *sendData, uint32_t sendDataSize, uint8_t *receiveData, uint32_t *receiveDataSize) const { + ob_error *error = nullptr; + ob_device_send_and_receive_data(impl_, sendData, sendDataSize, receiveData, receiveDataSize, &error); + Error::handle(&error); + } + + /** + * @brief Check if the device supports the frame interleave feature. + * + * @return bool Returns true if the device supports the frame interleave feature. + */ + bool isFrameInterleaveSupported() const { + ob_error *error = nullptr; + bool ret = ob_device_is_frame_interleave_supported(impl_, &error); + Error::handle(&error); + return ret; + } + + /** + * @brief load the frame interleave according to frame interleave name. + * @param frameInterleaveName The frame interleave name to set. The name should be one of the frame interleave names returned by @ref + * getAvailableFrameInterleaveList. + */ + void loadFrameInterleave(const char *frameInterleaveName) const { + ob_error *error = nullptr; + ob_device_load_frame_interleave(impl_, frameInterleaveName, &error); + Error::handle(&error); + } + + /** + * @brief Get available frame interleave list + * + * @return DeviceFrameInterleaveList return the available frame interleave list. + */ + std::shared_ptr getAvailableFrameInterleaveList() const { + ob_error *error = nullptr; + auto list = ob_device_get_available_frame_interleave_list(impl_, &error); + Error::handle(&error); + return std::make_shared(list); + } + + /** + * @brief Get the available preset resolution config list + * + * @return PresetResolutionConfigList return the available preset resolution config list. + */ + std::shared_ptr getAvailablePresetResolutionConfigList() const { + ob_error *error = nullptr; + auto list = ob_device_get_available_preset_resolution_config_list(impl_, &error); + Error::handle(&error); + return std::make_shared(list); + } + +private: + static void firmwareUpdateCallback(ob_fw_update_state state, const char *message, uint8_t percent, void *userData) { + auto device = static_cast(userData); + if(device && device->fwUpdateCallback_) { + device->fwUpdateCallback_(state, message, percent); + } + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + void deviceUpgrade(const char *filePath, DeviceFwUpdateCallback callback, bool async = true) { + updateFirmware(filePath, callback, async); + } + + void deviceUpgradeFromData(const uint8_t *firmwareData, uint32_t firmwareDataSize, DeviceFwUpdateCallback callback, bool async = true) { + updateFirmwareFromData(firmwareData, firmwareDataSize, callback, async); + } + + std::shared_ptr getCalibrationCameraParamList() { + ob_error *error = nullptr; + auto impl = ob_device_get_calibration_camera_param_list(impl_, &error); + Error::handle(&error); + return std::make_shared(impl); + } + + void loadDepthFilterConfig(const char *filePath) { + // In order to compile, some high-version compilers will warn that the function parameters are not used. + (void)filePath; + } +}; + +/** + * @brief A class describing device information, representing the name, id, serial number and other basic information of an RGBD camera. + */ +class DeviceInfo { +private: + ob_device_info_t *impl_ = nullptr; + +public: + explicit DeviceInfo(ob_device_info_t *impl) : impl_(impl) {} + ~DeviceInfo() noexcept { + ob_error *error = nullptr; + ob_delete_device_info(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get device name + * + * @return const char * return the device name + */ + const char *getName() const { + ob_error *error = nullptr; + const char *name = ob_device_info_get_name(impl_, &error); + Error::handle(&error); + return name; + } + + /** + * @brief Get the pid of the device + * + * @return int return the pid of the device + */ + int getPid() const { + ob_error *error = nullptr; + int pid = ob_device_info_get_pid(impl_, &error); + Error::handle(&error); + return pid; + } + + /** + * @brief Get the vid of the device + * + * @return int return the vid of the device + */ + int getVid() const { + ob_error *error = nullptr; + int vid = ob_device_info_get_vid(impl_, &error); + Error::handle(&error); + return vid; + } + + /** + * @brief Get system assigned uid for distinguishing between different devices + * + * @return const char * return the uid of the device + */ + const char *getUid() const { + ob_error *error = nullptr; + const char *uid = ob_device_info_get_uid(impl_, &error); + Error::handle(&error); + return uid; + } + + /** + * @brief Get the serial number of the device + * + * @return const char * return the serial number of the device + */ + const char *getSerialNumber() const { + ob_error *error = nullptr; + const char *sn = ob_device_info_get_serial_number(impl_, &error); + Error::handle(&error); + return sn; + } + + /** + * @brief Get the version number of the firmware + * + * @return const char* return the version number of the firmware + */ + const char *getFirmwareVersion() const { + ob_error *error = nullptr; + const char *version = ob_device_info_get_firmware_version(impl_, &error); + Error::handle(&error); + return version; + } + + /** + * @brief Get the connection type of the device + * + * @return const char* the connection type of the device, currently supports: "USB", "USB1.0", "USB1.1", "USB2.0", "USB2.1", "USB3.0", "USB3.1", + * "USB3.2", "Ethernet" + */ + const char *getConnectionType() const { + ob_error *error = nullptr; + const char *type = ob_device_info_get_connection_type(impl_, &error); + Error::handle(&error); + return type; + } + + /** + * @brief Get the IP address of the device + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0". + * + * @return const char* the IP address of the device, such as "192.168.1.10" + */ + const char *getIpAddress() const { + ob_error *error = nullptr; + const char *ip = ob_device_info_get_ip_address(impl_, &error); + Error::handle(&error); + return ip; + } + + /** + * @brief Get the version number of the hardware + * + * @return const char* the version number of the hardware + */ + const char *getHardwareVersion() const { + ob_error *error = nullptr; + const char *version = ob_device_info_get_hardware_version(impl_, &error); + Error::handle(&error); + return version; + } + + /** + * @brief Get the minimum version number of the SDK supported by the device + * + * @return const char* the minimum SDK version number supported by the device + */ + const char *getSupportedMinSdkVersion() const { + ob_error *error = nullptr; + const char *version = ob_device_info_get_supported_min_sdk_version(impl_, &error); + Error::handle(&error); + return version; + } + + /** + * @brief Get chip type name + * + * @return const char* the chip type name + */ + const char *getAsicName() const { + ob_error *error = nullptr; + const char *name = ob_device_info_get_asicName(impl_, &error); + Error::handle(&error); + return name; + } + + /** + * @brief Get the device type + * + * @return OBDeviceType the device type + */ + OBDeviceType getDeviceType() const { + ob_error *error = nullptr; + OBDeviceType type = ob_device_info_get_device_type(impl_, &error); + Error::handle(&error); + return type; + } + + /** + * @brief Get the subnet mask of the device + * + * @return const char* the subnet mask of the device, such as "255.255.255.0" + */ + const char *getDeviceSubnetMask() const { + ob_error *error = nullptr; + const char *subnetMask = ob_device_info_get_subnet_mask(impl_, &error); + Error::handle(&error); + return subnetMask; + } + + /** + * @brief Get the gateway address of the device + * + * @return const char* the gateway address of the device, such as "192.168.1.1" + */ + const char *getDevicegateway() const { + ob_error *error = nullptr; + const char *subnetMask = ob_device_info_get_gateway(impl_, &error); + Error::handle(&error); + return subnetMask; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + const char *name() const { + return getName(); + } + + int pid() const { + return getPid(); + } + + int vid() const { + return getVid(); + } + + const char *uid() const { + return getUid(); + } + + const char *serialNumber() const { + return getSerialNumber(); + } + + const char *firmwareVersion() const { + return getFirmwareVersion(); + } + + const char *connectionType() const { + return getConnectionType(); + } + + const char *ipAddress() const { + return getIpAddress(); + } + + const char *hardwareVersion() const { + return getHardwareVersion(); + } + + const char *supportedMinSdkVersion() const { + return getSupportedMinSdkVersion(); + } + + const char *asicName() const { + return getAsicName(); + } + + OBDeviceType deviceType() const { + return getDeviceType(); + } +}; + +/** + * @brief Class representing a list of devices + */ +class DeviceList { +private: + ob_device_list_t *impl_ = nullptr; + +public: + explicit DeviceList(ob_device_list_t *impl) : impl_(impl) {} + ~DeviceList() noexcept { + ob_error *error = nullptr; + ob_delete_device_list(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get the number of devices in the list + * + * @return uint32_t the number of devices in the list + */ + uint32_t getCount() const { + ob_error *error = nullptr; + auto count = ob_device_list_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Get the PID of the device at the specified index + * + * @param index the index of the device + * @return int the PID of the device + */ + int getPid(uint32_t index) const { + ob_error *error = nullptr; + auto pid = ob_device_list_get_device_pid(impl_, index, &error); + Error::handle(&error); + return pid; + } + + /** + * @brief Get the VID of the device at the specified index + * + * @param index the index of the device + * @return int the VID of the device + */ + int getVid(uint32_t index) const { + ob_error *error = nullptr; + auto vid = ob_device_list_get_device_vid(impl_, index, &error); + Error::handle(&error); + return vid; + } + + /** + * @brief Get the UID of the device at the specified index + * + * @param index the index of the device + * @return const char* the UID of the device + */ + const char *getUid(uint32_t index) const { + ob_error *error = nullptr; + auto uid = ob_device_list_get_device_uid(impl_, index, &error); + Error::handle(&error); + return uid; + } + + /** + * @brief Get the serial number of the device at the specified index + * + * @param index the index of the device + * @return const char* the serial number of the device + */ + const char *getSerialNumber(uint32_t index) const { + ob_error *error = nullptr; + auto sn = ob_device_list_get_device_serial_number(impl_, index, &error); + Error::handle(&error); + return sn; + } + + /** + * @brief Get the name of the device at the specified index in the device list. + * + * This function retrieves the name of the device at the given index in the device list. + * If an error occurs during the operation, it will be handled by the Error::handle function. + * + * @param index The index of the device in the device list. + * @return const char* The name of the device at the specified index. + */ + const char *getName(uint32_t index) const { + ob_error *error = nullptr; + auto name = ob_device_list_get_device_name(impl_, index, &error); + Error::handle(&error); + return name; + } + + /** + * @brief Get device connection type + * + * @param index device index + * @return const char* returns connection type, currently supports: "USB", "USB1.0", "USB1.1", "USB2.0", "USB2.1", "USB3.0", "USB3.1", "USB3.2", "Ethernet" + */ + const char *getConnectionType(uint32_t index) const { + ob_error *error = nullptr; + auto type = ob_device_list_get_device_connection_type(impl_, index, &error); + Error::handle(&error); + return type; + } + + /** + * @brief get the ip address of the device at the specified index + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0". + * + * @param index the index of the device + * @return const char* the ip address of the device + */ + const char *getIpAddress(uint32_t index) const { + ob_error *error = nullptr; + auto ip = ob_device_list_get_device_ip_address(impl_, index, &error); + Error::handle(&error); + return ip; + } + + /** + * @brief get the subnet mask of the device at the specified index + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0". + * + * @param index the index of the device + * @return const char* the subnet mask of the device + */ + const char *getSubnetMask(uint32_t index) const { + ob_error *error = nullptr; + auto subnetMask = ob_device_list_get_device_subnet_mask(impl_, index, &error); + Error::handle(&error); + return subnetMask; + } + + /** + * @brief get the gateway of the device at the specified index + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0". + * + * @param index the index of the device + * @return const char* the gateway of the device + */ + const char *getGateway(uint32_t index) const { + ob_error *error = nullptr; + auto gateway = ob_device_list_get_device_gateway(impl_, index, &error); + Error::handle(&error); + return gateway; + } + + /** + * @brief Get the MAC address of the host network interface corresponding to the device at the specified index + * + * @attention Only valid for network devices, otherwise it will return "0:0:0:0:0:0". + * + * @param index the index of the device + * @return const char* The MAC address of the host network interface associated with the device. + */ + const char *getLocalMacAddress(uint32_t index) const { + ob_error *error = nullptr; + auto mac = ob_device_list_get_device_local_mac(impl_, index, &error); + Error::handle(&error); + return mac; + } + + /** + * @brief Get the IP address of the host network interface corresponding to the device at the specified index + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0". + * + * @param index The index of the device + * @return const char* The IP address of the host network interface associated with the device. + */ + const char *getLocalIP(uint32_t index) const { + ob_error *error = nullptr; + auto ip = ob_device_list_get_device_local_ip(impl_, index, &error); + Error::handle(&error); + return ip; + } + + /** + * @brief Get the subnet length of the host network interface corresponding to the device at the specified index + * + * @attention Only valid for network devices, otherwise it will return 0. + * + * @param index The index of the device + * @return uint8_t The subnet length (0~32) of the host network interface associated with the device. + */ + uint8_t getLocalSubnetLength(uint32_t index) const { + ob_error *error = nullptr; + auto subnetMask = ob_device_list_get_device_local_subnet_length(impl_, index, &error); + Error::handle(&error); + return subnetMask; + } + + /** + * @brief Get the gateway of the host network interface corresponding to the device at the specified index + * + * @attention Only valid for network devices, otherwise it will return "0.0.0.0". + * + * @param index The index of the device + * @return const char* The gateway of the host network interface associated with the device. + */ + const char *getLocalGateway(uint32_t index) const { + ob_error *error = nullptr; + auto gateway = ob_device_list_get_device_local_gateway(impl_, index, &error); + Error::handle(&error); + return gateway; + } + + /** + * @brief Get the device object at the specified index + * + * @attention If the device has already been acquired and created elsewhere, repeated acquisition will throw an exception + * + * @param index the index of the device to create + * @return std::shared_ptr the device object + */ + std::shared_ptr getDevice(uint32_t index) const { + ob_error *error = nullptr; + auto device = ob_device_list_get_device(impl_, index, &error); + Error::handle(&error); + return std::make_shared(device); + } + + /** + * @brief Get the device object with the specified serial number + * + * @attention If the device has already been acquired and created elsewhere, repeated acquisition will throw an exception + * + * @param serialNumber the serial number of the device to create + * @return std::shared_ptr the device object + */ + std::shared_ptr getDeviceBySN(const char *serialNumber) const { + ob_error *error = nullptr; + auto device = ob_device_list_get_device_by_serial_number(impl_, serialNumber, &error); + Error::handle(&error); + return std::make_shared(device); + } + + /** + * @brief Get the specified device object from the device list by uid + * @brief On Linux platform, for usb device, the uid of the device is composed of bus-port-dev, for example 1-1.2-1. But the SDK will remove the dev number + * and only keep the bus-port as the uid to create the device, for example 1-1.2, so that we can create a device connected to the specified USB port. + * Similarly, users can also directly pass in bus-port as uid to create device. + * @brief For GMSL device, the uid is GMSL port with "gmsl2-" prefix, for example gmsl2-1. + * + * @attention If the device has been acquired and created elsewhere, repeated acquisition will throw an exception + * + * @param uid The uid of the device to be created + * @return std::shared_ptr returns the device object + */ + std::shared_ptr getDeviceByUid(const char *uid) const { + ob_error *error = nullptr; + auto device = ob_device_list_get_device_by_uid(impl_, uid, &error); + Error::handle(&error); + return std::make_shared(device); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t deviceCount() const { + return getCount(); + } + + int pid(uint32_t index) const { + return getPid(index); + } + + int vid(uint32_t index) const { + return getVid(index); + } + + const char *uid(uint32_t index) const { + return getUid(index); + } + + const char *serialNumber(uint32_t index) const { + return getSerialNumber(index); + } + + const char *name(uint32_t index) const { + return getName(index); + } + + const char *connectionType(uint32_t index) const { + return getConnectionType(index); + } + + const char *ipAddress(uint32_t index) const { + return getIpAddress(index); + } +}; + +class OBDepthWorkModeList { +private: + ob_depth_work_mode_list_t *impl_ = nullptr; + +public: + explicit OBDepthWorkModeList(ob_depth_work_mode_list_t *impl) : impl_(impl) {} + ~OBDepthWorkModeList() { + ob_error *error = nullptr; + ob_delete_depth_work_mode_list(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get the number of OBDepthWorkMode objects in the list + * + * @return uint32_t the number of OBDepthWorkMode objects in the list + */ + uint32_t getCount() { + ob_error *error = nullptr; + auto count = ob_depth_work_mode_list_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Get the OBDepthWorkMode object at the specified index + * + * @param index the index of the target OBDepthWorkMode object + * @return OBDepthWorkMode the OBDepthWorkMode object at the specified index + */ + OBDepthWorkMode getOBDepthWorkMode(uint32_t index) { + ob_error *error = nullptr; + auto mode = ob_depth_work_mode_list_get_item(impl_, index, &error); + Error::handle(&error); + return mode; + } + + /** + * @brief Get the OBDepthWorkMode object at the specified index + * + * @param index the index of the target OBDepthWorkMode object + * @return OBDepthWorkMode the OBDepthWorkMode object at the specified index + */ + OBDepthWorkMode operator[](uint32_t index) { + return getOBDepthWorkMode(index); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t count() { + return getCount(); + } +}; + +/** + * @brief Class representing a list of device presets + * @brief A device preset is a set of parameters or configurations that can be applied to the device to achieve a specific effect or function. + */ +class DevicePresetList { +private: + ob_device_preset_list_t *impl_ = nullptr; + +public: + explicit DevicePresetList(ob_device_preset_list_t *impl) : impl_(impl) {} + ~DevicePresetList() noexcept { + ob_error *error = nullptr; + ob_delete_preset_list(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get the number of device presets in the list + * + * @return uint32_t the number of device presets in the list + */ + uint32_t getCount() { + ob_error *error = nullptr; + auto count = ob_device_preset_list_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Get the name of the device preset at the specified index + * + * @param index the index of the device preset + * @return const char* the name of the device preset + */ + const char *getName(uint32_t index) { + ob_error *error = nullptr; + const char *name = ob_device_preset_list_get_name(impl_, index, &error); + Error::handle(&error); + return name; + } + + /** + * @brief check if the preset list contains the special name preset. + * @param name The name of the preset + * @return bool Returns true if the special name is found in the preset list, otherwise returns false. + */ + bool hasPreset(const char *name) { + ob_error *error = nullptr; + auto result = ob_device_preset_list_has_preset(impl_, name, &error); + Error::handle(&error); + return result; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t count() { + return getCount(); + } +}; + +/** + * @brief Class representing a list of camera parameters + */ +class CameraParamList { +private: + ob_camera_param_list_t *impl_ = nullptr; + +public: + explicit CameraParamList(ob_camera_param_list_t *impl) : impl_(impl) {} + ~CameraParamList() noexcept { + ob_error *error = nullptr; + ob_delete_camera_param_list(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get the number of camera parameters in the list. + * + * @return uint32_t the number of camera parameters in the list. + */ + uint32_t getCount() { + ob_error *error = nullptr; + auto count = ob_camera_param_list_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Get the camera parameters for the specified index + * + * @param index the index of the parameter group + * @return OBCameraParam the corresponding group parameters + */ + OBCameraParam getCameraParam(uint32_t index) { + ob_error *error = nullptr; + auto param = ob_camera_param_list_get_param(impl_, index, &error); + Error::handle(&error); + return param; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t count() { + return getCount(); + } +}; + +/** + * @brief Class representing a list of device Frame Interleave + */ +class DeviceFrameInterleaveList { +private: + ob_device_frame_interleave_list_t *impl_ = nullptr; + +public: + explicit DeviceFrameInterleaveList(ob_device_frame_interleave_list_t *impl) : impl_(impl) {} + ~DeviceFrameInterleaveList() noexcept { + ob_error *error = nullptr; + ob_delete_frame_interleave_list(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get the number of device frame interleave in the list + * + * @return uint32_t the number of device frame interleave in the list + */ + uint32_t getCount() { + ob_error *error = nullptr; + auto count = ob_device_frame_interleave_list_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Get the name of the device frame interleave at the specified index + * + * @param index the index of the device frame interleave + * @return const char* the name of the device frame interleave + */ + const char *getName(uint32_t index) { + ob_error *error = nullptr; + const char *name = ob_device_frame_interleave_list_get_name(impl_, index, &error); + Error::handle(&error); + return name; + } + + /** + * @brief check if the frame interleave list contains the special name frame interleave. + * @param name The name of the frame interleave + * @return bool Returns true if the special name is found in the frame interleave list, otherwise returns false. + */ + bool hasFrameInterleave(const char *name) { + ob_error *error = nullptr; + auto result = ob_device_frame_interleave_list_has_frame_interleave(impl_, name, &error); + Error::handle(&error); + return result; + } +}; + +/** + * @brief Class representing a list of preset resolution config list + */ +class PresetResolutionConfigList { +private: + ob_preset_resolution_config_list_t *impl_ = nullptr; + +public: + explicit PresetResolutionConfigList(ob_preset_resolution_config_list_t *impl) : impl_(impl) {} + ~PresetResolutionConfigList() noexcept { + ob_error *error = nullptr; + ob_delete_preset_resolution_config_list(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get the number of device preset resolution ratio in the list + */ + uint32_t getCount() { + ob_error *error = nullptr; + auto count = ob_device_preset_resolution_config_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /* + * @brief Get the device preset resolution ratio at the specified index + * @param index the index of the device preset resolution ratio + * @return OBPresetResolutionConfig the corresponding device preset resolution ratio + */ + OBPresetResolutionConfig getPresetResolutionRatioConfig(uint32_t index) { + ob_error *error = nullptr; + auto config = ob_device_preset_resolution_config_list_get_item(impl_, index, &error); + Error::handle(&error); + return config; + } +}; + +} // namespace ob diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Error.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Error.hpp new file mode 100644 index 0000000..d0c099a --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Error.hpp @@ -0,0 +1,116 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Error.hpp + * @brief This file defines the Error class, which describes abnormal errors within the SDK. + * Detailed information about the exception can be obtained through this class. + */ +#pragma once + +#include "Types.hpp" +#include "libobsensor/h/Error.h" +#include + +namespace ob { +class Error : public std::exception { +private: + ob_error *impl_; + + /** + * @brief Construct a new Error object + * + * @attention This constructor should not be called directly, use the handle() function instead. + * + * @param error The ob_error object + */ + explicit Error(ob_error *error) : impl_(error) {}; + + Error& operator=(const Error&) = default; + +public: + /** + * @brief A static function to handle the ob_error and throw an exception if needed. + * + * @param error The ob_error pointer to be handled. + * @param throw_exception A boolean value to indicate whether to throw an exception or not, the default value is true. + */ + static void handle(ob_error **error, bool throw_exception = true) { + if(!error || !*error) { // no error + return; + } + + if(throw_exception) { + throw Error(*error); + } + else { + ob_delete_error(*error); + *error = nullptr; + } + } + + /** + * @brief Destroy the Error object + */ + ~Error() override { + if(impl_) { + ob_delete_error(impl_); + impl_ = nullptr; + } + } + + /** + * @brief Returns the error message of the exception. + * + * @return const char* The error message. + */ + const char *what() const noexcept override { + return impl_->message; + } + + /** + * @brief Returns the exception type of the exception. + * @brief Read the comments of the OBExceptionType enum in the libobsensor/h/ObTypes.h file for more information. + * + * @return OBExceptionType The exception type. + */ + OBExceptionType getExceptionType() const noexcept { + return impl_->exception_type; + } + + /** + * @brief Returns the name of the function where the exception occurred. + * + * @return const char* The function name. + */ + const char *getFunction() const noexcept { + return impl_->function; + } + + /** + * @brief Returns the arguments of the function where the exception occurred. + * + * @return const char* The arguments. + */ + const char *getArgs() const noexcept { + return impl_->args; + } + + /** + * @brief Returns the error message of the exception. + * @brief It is recommended to use the what() function instead. + * + * @return const char* The error message. + */ + const char *getMessage() const noexcept { + return impl_->message; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + const char *getName() const noexcept { + return impl_->function; + } +}; +} // namespace ob + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Filter.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Filter.hpp new file mode 100644 index 0000000..dc291cf --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Filter.hpp @@ -0,0 +1,1190 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Filter.hpp + * @brief This file contains the Filter class, which is the processing unit of the SDK that can perform point cloud generation, format conversion, and other + * functions. + */ +#pragma once + +#include "Types.hpp" +#include "Error.hpp" +#include "Frame.hpp" +#include "libobsensor/h/Filter.h" +#include "libobsensor/h/Frame.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ob { + +/** + * @brief A callback function that takes a shared pointer to a Frame object as its argument. + */ +typedef std::function)> FilterCallback; + +/** + * @brief Get the type of a PropertyRange member + */ +template struct RangeTraits { + using valueType = void; +}; + +template <> struct RangeTraits { + using valueType = uint8_t; +}; + +template <> struct RangeTraits { + using valueType = uint16_t; +}; + +template <> struct RangeTraits { + using valueType = uint32_t; +}; + +template <> struct RangeTraits { + using valueType = float; +}; + +/** + * @brief Get T Property Range + */ +template T getPropertyRange(const OBFilterConfigSchemaItem &item, const double cur) { + // If T type is illegal, T will be void + using valueType = typename RangeTraits::valueType; + T range{}; + // Compilate error will be reported here if T is void + range.cur = static_cast(cur); + range.def = static_cast(item.def); + range.max = static_cast(item.max); + range.min = static_cast(item.min); + range.step = static_cast(item.step); + return range; +} + +/** + * @brief The Filter class is the base class for all filters in the SDK. + */ +class Filter : public std::enable_shared_from_this { +protected: + ob_filter *impl_ = nullptr; + std::string name_; + FilterCallback callback_; + std::vector configSchemaVec_; + +protected: + /** + * @brief Default constructor with nullptr impl, used for derived classes only. + */ + Filter() = default; + + virtual void init(ob_filter *impl) { + impl_ = impl; + ob_error *error = nullptr; + name_ = ob_filter_get_name(impl_, &error); + Error::handle(&error); + + auto configSchemaList = ob_filter_get_config_schema_list(impl_, &error); + Error::handle(&error); + + auto count = ob_filter_config_schema_list_get_count(configSchemaList, &error); + Error::handle(&error); + + for(uint32_t i = 0; i < count; i++) { + auto item = ob_filter_config_schema_list_get_item(configSchemaList, i, &error); + Error::handle(&error); + configSchemaVec_.emplace_back(item); + } + + ob_delete_filter_config_schema_list(configSchemaList, &error); + Error::handle(&error); + } + +public: + explicit Filter(ob_filter *impl) { + init(impl); + } + + virtual ~Filter() noexcept { + if(impl_ != nullptr) { + ob_error *error = nullptr; + ob_delete_filter(impl_, &error); + Error::handle(&error, false); + } + } + + /** + * @brief Get the Impl object of the filter. + * + * @return ob_filter* The Impl object of the filter. + */ + ob_filter *getImpl() const { + return impl_; + } + + /** + * @brief Get the type of filter. + * + * @return string The type of filte. + */ + virtual const std::string &getName() const { + return name_; + } + + /** + * @brief Reset the filter, freeing the internal cache, stopping the processing thread, and clearing the pending buffer frame when asynchronous processing + * is used. + */ + virtual void reset() const { + ob_error *error = nullptr; + ob_filter_reset(impl_, &error); + Error::handle(&error); + } + + /** + * @brief enable the filter + */ + virtual void enable(bool enable) const { + ob_error *error = nullptr; + ob_filter_enable(impl_, enable, &error); + Error::handle(&error); + } + + /** + * @brief Return Enable State + */ + virtual bool isEnabled() const { + ob_error *error = nullptr; + bool enable = ob_filter_is_enabled(impl_, &error); + Error::handle(&error); + return enable; + } + + /** + * @brief Processes a frame synchronously. + * + * @param frame The frame to be processed. + * @return std::shared_ptr< Frame > The processed frame. + */ + virtual std::shared_ptr process(std::shared_ptr frame) const { + ob_error *error = nullptr; + auto result = ob_filter_process(impl_, frame->getImpl(), &error); + Error::handle(&error); + if(!result) { + return nullptr; + } + return std::make_shared(result); + } + + /** + * @brief Pushes the pending frame into the cache for asynchronous processing. + * + * @param frame The pending frame. The processing result is returned by the callback function. + */ + virtual void pushFrame(std::shared_ptr frame) const { + ob_error *error = nullptr; + ob_filter_push_frame(impl_, frame->getImpl(), &error); + Error::handle(&error); + } + + /** + * @brief Set the callback function for asynchronous processing. + * + * @param callback The processing result callback. + */ + virtual void setCallBack(FilterCallback callback) { + callback_ = callback; + ob_error *error = nullptr; + ob_filter_set_callback(impl_, &Filter::filterCallback, this, &error); + Error::handle(&error); + } + + /** + * @brief Get config schema of the filter + * @brief The returned string is a csv format string representing the configuration schema of the filter. The format of the string is: + * , , , , , , + * + * @return std::string The config schema of the filter. + */ + virtual std::string getConfigSchema() const { + ob_error *error = nullptr; + auto schema = ob_filter_get_config_schema(impl_, &error); + Error::handle(&error); + return schema; + } + + /** + * @brief Get the Config Schema Vec object + * @brief The returned vector contains the config schema items. Each item in the vector is an @ref OBFilterConfigSchemaItem object. + * + * @return std::vector The vector of the filter config schema. + */ + virtual std::vector getConfigSchemaVec() const { + return configSchemaVec_; + } + + /** + * @brief Set the filter config value by name. + * + * @attention The pass into value type is double, witch will be cast to the actual type inside the filter. The actual type can be queried by the filter + * config schema returned by @ref getConfigSchemaVec. + * + * @param configName The name of the config. + * @param value The value of the config. + */ + virtual void setConfigValue(const std::string &configName, double value) const { + ob_error *error = nullptr; + ob_filter_set_config_value(impl_, configName.c_str(), value, &error); + Error::handle(&error); + } + + /** + * @brief Get the Config Value object by name. + * + * @attention The returned value type has been casted to double inside the filter. The actual type can be queried by the filter config schema returned by + * @ref getConfigSchemaVec. + * + * @param configName The name of the config. + * @return double The value of the config. + */ + virtual double getConfigValue(const std::string &configName) const { + ob_error *error = nullptr; + double value = ob_filter_get_config_value(impl_, configName.c_str(), &error); + Error::handle(&error); + return value; + } + +private: + static void filterCallback(ob_frame *frame, void *userData) { + auto filter = static_cast(userData); + filter->callback_(std::make_shared(frame)); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + virtual const char *type() { + return getName().c_str(); + } + + /** + * @brief Check if the runtime type of the filter object is compatible with a given type. + * + * @tparam T The given type. + * @return bool The result. + */ + template bool is(); + + template std::shared_ptr as() { + if(!is()) { + throw std::runtime_error("unsupported operation, object's type is not require type"); + } + + return std::static_pointer_cast(shared_from_this()); + } +}; + +/** + * @brief A factory class for creating filters. + */ +class FilterFactory { +public: + /** + * @brief Create a filter by name. + */ + static std::shared_ptr createFilter(const std::string &name) { + ob_error *error = nullptr; + auto impl = ob_create_filter(name.c_str(), &error); + Error::handle(&error); + return std::make_shared(impl); + } + + /** + * @brief Create a private filter by name and activation key. + * @brief Some private filters require an activation key to be activated, its depends on the vendor of the filter. + * + * @param name The name of the filter. + * @param activationKey The activation key of the filter. + */ + static std::shared_ptr createPrivateFilter(const std::string &name, const std::string &activationKey) { + ob_error *error = nullptr; + auto impl = ob_create_private_filter(name.c_str(), activationKey.c_str(), &error); + Error::handle(&error); + return std::make_shared(impl); + } + + /** + * @brief Get the vendor specific code of a filter by filter name. + * @brief A private filter can define its own vendor specific code for specific purposes. + * + * @param name The name of the filter. + * @return std::string The vendor specific code of the filter. + */ + static std::string getFilterVendorSpecificCode(const std::string &name) { + ob_error *error = nullptr; + auto code = ob_filter_get_vendor_specific_code(name.c_str(), &error); + Error::handle(&error); + return code; + } +}; + +/** + * @brief The PointCloudFilter class is a subclass of Filter that generates point clouds. + */ +class PointCloudFilter : public Filter { +public: + PointCloudFilter() { + ob_error *error = nullptr; + auto impl = ob_create_filter("PointCloudFilter", &error); + Error::handle(&error); + init(impl); + } + + virtual ~PointCloudFilter() noexcept override = default; + + /** + * @brief Set the output pointcloud frame format. + * + * @param format The point cloud frame format: OB_FORMAT_POINT or OB_FORMAT_RGB_POINT + */ + void setCreatePointFormat(OBFormat format) { + setConfigValue("pointFormat", static_cast(format)); + } + + /** + * @brief Set the point cloud coordinate data zoom factor. + * + * @brief Calling this function to set the scale will change the point coordinate scaling factor of the output point cloud frame, The point coordinate + * scaling factor for the output point cloud frame can be obtained via @ref PointsFrame::getCoordinateValueScale function. + * + * @param factor The scale factor. + */ + void setCoordinateDataScaled(float factor) { + setConfigValue("coordinateDataScale", factor); + } + + /** + * @brief Set point cloud color data normalization. + * @brief If normalization is required, the output point cloud frame's color data will be normalized to the range [0, 1]. + * + * @attention This function only works for when create point format is set to OB_FORMAT_RGB_POINT. + * + * @param state Whether normalization is required. + */ + void setColorDataNormalization(bool state) { + setConfigValue("colorDataNormalization", state); + } + + /** + * @brief Set the point cloud coordinate system. + * + * @param type The coordinate system type. + */ + void setCoordinateSystem(OBCoordinateSystemType type) { + setConfigValue("coordinateSystemType", static_cast(type)); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + void setPositionDataScaled(float scale) { + setCoordinateDataScaled(scale); + } + + // The following interfaces are deprecated and are retained here for compatibility purposes. + void setFrameAlignState(bool state) { + (void)state; // to complie + } + // The following interfaces are deprecated and are retained here for compatibility purposes. + void setCameraParam(OBCameraParam param) { + (void)param; + } +}; + +/** + * @brief Align for depth to other or other to depth. + */ +class Align : public Filter { +public: + Align(OBStreamType alignToStreamType) { + ob_error *error = nullptr; + auto impl = ob_create_filter("Align", &error); + Error::handle(&error); + init(impl); + + setConfigValue("AlignType", static_cast(alignToStreamType)); + } + + virtual ~Align() noexcept override = default; + + OBStreamType getAlignToStreamType() { + return static_cast(static_cast(getConfigValue("AlignType"))); + } + + /** + * @brief Sets whether the output frame resolution should match the target resolution. + * When enabled, the output frame resolution will be adjusted to match (same as) the target resolution. + * When disabled, the output frame resolution will match the original resolution while maintaining + * the aspect ratio of the target resolution. + * + * + * @param state If true, output frame resolution will match the target resolution; otherwise, it will + * maintain the original resolution with the target's aspect ratio. + */ + void setMatchTargetResolution(bool state) { + setConfigValue("MatchTargetRes", state); + } + + /** + * @brief Set the Align To Stream Profile + * @brief It is useful when the align target stream dose not started (without any frame to get intrinsics and extrinsics). + * + * @param profile The Align To Stream Profile. + */ + void setAlignToStreamProfile(std::shared_ptr profile) { + ob_error *error = nullptr; + ob_align_filter_set_align_to_stream_profile(impl_, profile->getImpl(), &error); + Error::handle(&error); + } +}; + +/** + * @brief The FormatConvertFilter class is a subclass of Filter that performs format conversion. + */ +class FormatConvertFilter : public Filter { +public: + FormatConvertFilter() { + ob_error *error = nullptr; + auto impl = ob_create_filter("FormatConverter", &error); + Error::handle(&error); + init(impl); + } + + virtual ~FormatConvertFilter() noexcept override = default; + + /** + * @brief Set the format conversion type. + * + * @param type The format conversion type. + */ + void setFormatConvertType(OBConvertFormat type) { + setConfigValue("convertType", static_cast(type)); + } +}; + +/** + * @brief HdrMerge processing block, + * the processing merges between depth frames with + * different sub-preset sequence ids. + */ +class HdrMerge : public Filter { +public: + HdrMerge() { + ob_error *error = nullptr; + auto impl = ob_create_filter("HDRMerge", &error); + Error::handle(&error); + init(impl); + } + + virtual ~HdrMerge() noexcept override = default; +}; + +/** + * @brief Create SequenceIdFilter processing block. + */ +class SequenceIdFilter : public Filter { +private: + std::map sequenceIdList_{ { 0.f, "all" }, { 1.f, "1" } }; + OBSequenceIdItem *outputSequenceIdList_ = nullptr; + + void initSequenceIdList() { + outputSequenceIdList_ = new OBSequenceIdItem[sequenceIdList_.size()]; + + int i = 0; + for(const auto &pair: sequenceIdList_) { + outputSequenceIdList_[i].sequenceSelectId = static_cast(pair.first); + strncpy(outputSequenceIdList_[i].name, pair.second.c_str(), sizeof(outputSequenceIdList_[i].name) - 1); + outputSequenceIdList_[i].name[sizeof(outputSequenceIdList_[i].name) - 1] = '\0'; + ++i; + } + } + +public: + SequenceIdFilter() { + ob_error *error = nullptr; + auto impl = ob_create_filter("SequenceIdFilter", &error); + Error::handle(&error); + init(impl); + initSequenceIdList(); + } + + virtual ~SequenceIdFilter() noexcept override { + if(outputSequenceIdList_) { + delete[] outputSequenceIdList_; + outputSequenceIdList_ = nullptr; + } + } + + /** + * @brief Set the sequenceId filter params. + * + * @param sequence_id id to pass the filter. + */ + void selectSequenceId(int sequence_id) { + setConfigValue("sequenceid", static_cast(sequence_id)); + } + + /** + * @brief Get the current sequence id. + * + * @return sequence id to pass the filter. + */ + int getSelectSequenceId() { + return static_cast(getConfigValue("sequenceid")); + } + + OBSequenceIdItem *getSequenceIdList() { + return outputSequenceIdList_; + } + + /** + * @brief Get the sequenceId list size. + * + * @return the size of sequenceId list. + */ + int getSequenceIdListSize() { + return static_cast(sequenceIdList_.size()); + } +}; + +/** + * @brief Decimation filter, reducing complexity by subsampling depth maps and losing depth details. + */ +class DecimationFilter : public Filter { +public: + DecimationFilter() { + ob_error *error = nullptr; + auto impl = ob_create_filter("DecimationFilter", &error); + Error::handle(&error); + init(impl); + } + + virtual ~DecimationFilter() noexcept override = default; + + /** + * @brief Set the decimation filter scale value. + * + * @param value The decimation filter scale value. + */ + void setScaleValue(uint8_t value) { + setConfigValue("decimate", static_cast(value)); + } + + /** + * @brief Get the decimation filter scale value. + */ + uint8_t getScaleValue() { + return static_cast(getConfigValue("decimate")); + } + + /** + * @brief Get the property range of the decimation filter scale value. + */ + OBUint8PropertyRange getScaleRange() { + OBUint8PropertyRange scaleRange{}; + if(configSchemaVec_.size() != 0) { + const auto &item = configSchemaVec_[0]; + scaleRange = getPropertyRange(item, getConfigValue("decimate")); + } + return scaleRange; + } +}; + +/** + * @brief Creates depth Thresholding filter + * By controlling min and max options on the block + */ +class ThresholdFilter : public Filter { +public: + ThresholdFilter() { + ob_error *error = nullptr; + auto impl = ob_create_filter("ThresholdFilter", &error); + Error::handle(&error); + init(impl); + } + + virtual ~ThresholdFilter() noexcept override = default; + + /** + * @brief Get the threshold filter min range. + * + * @return OBIntPropertyRange The range of the threshold filter min. + */ + OBIntPropertyRange getMinRange() { + OBIntPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "min") == 0) { + range = getPropertyRange(item, getConfigValue("min")); + break; + } + } + return range; + } + + /** + * @brief Get the threshold filter max range. + * + * @return OBIntPropertyRange The range of the threshold filter max. + */ + OBIntPropertyRange getMaxRange() { + OBIntPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "max") == 0) { + range = getPropertyRange(item, getConfigValue("max")); + break; + } + } + return range; + } + + /** + * @brief Set the threshold filter max and min range. + */ + bool setValueRange(uint16_t min, uint16_t max) { + if(min >= max) { + return false; + } + setConfigValue("min", min); + setConfigValue("max", max); + return true; + } +}; + +/** + * @brief Spatial advanced filte smooths the image by calculating frame with alpha and delta settings + * alpha defines the weight of the current pixel for smoothing, + * delta defines the depth gradient below which the smoothing will occur as number of depth levels. + */ +class SpatialAdvancedFilter : public Filter { +public: + SpatialAdvancedFilter(const std::string &activationKey = "") { + ob_error *error = nullptr; + auto impl = ob_create_private_filter("SpatialAdvancedFilter", activationKey.c_str(), &error); + Error::handle(&error); + init(impl); + } + + virtual ~SpatialAdvancedFilter() noexcept override = default; + + /** + * @brief Get the spatial advanced filter alpha range. + * + * @return OBFloatPropertyRange the alpha value of property range. + */ + OBFloatPropertyRange getAlphaRange() { + OBFloatPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "alpha") == 0) { + range = getPropertyRange(item, getConfigValue("alpha")); + break; + } + } + return range; + } + + /** + * @brief Get the spatial advanced filter dispdiff range. + * + * @return OBFloatPropertyRange the dispdiff value of property range. + */ + OBUint16PropertyRange getDispDiffRange() { + OBUint16PropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "disp_diff") == 0) { + range = getPropertyRange(item, getConfigValue("disp_diff")); + break; + } + } + return range; + } + + /** + * @brief Get the spatial advanced filter radius range. + * + * @return OBFloatPropertyRange the radius value of property range. + */ + OBUint16PropertyRange getRadiusRange() { + OBUint16PropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "radius") == 0) { + range = getPropertyRange(item, getConfigValue("radius")); + break; + } + } + return range; + } + + /** + * @brief Get the spatial advanced filter magnitude range. + * + * @return OBFloatPropertyRange the magnitude value of property range. + */ + OBIntPropertyRange getMagnitudeRange() { + OBIntPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "magnitude") == 0) { + range = getPropertyRange(item, getConfigValue("magnitude")); + break; + } + } + return range; + } + + /** + * @brief Get the spatial advanced filter params. + * + * @return OBSpatialAdvancedFilterParams + */ + OBSpatialAdvancedFilterParams getFilterParams() { + OBSpatialAdvancedFilterParams params{}; + params.alpha = static_cast(getConfigValue("alpha")); + params.disp_diff = static_cast(getConfigValue("disp_diff")); + params.magnitude = static_cast(getConfigValue("magnitude")); + params.radius = static_cast(getConfigValue("radius")); + return params; + } + + /** + * @brief Set the spatial advanced filter params. + * + * @param params OBSpatialAdvancedFilterParams. + */ + void setFilterParams(OBSpatialAdvancedFilterParams params) { + setConfigValue("alpha", params.alpha); + setConfigValue("disp_diff", params.disp_diff); + setConfigValue("magnitude", params.magnitude); + setConfigValue("radius", params.radius); + } +}; + +/** + * @brief The Spatial Fast Filter utilizes an enhanced median smoothing algorithm, + * designed to significantly reduce CPU usage and optimize processing efficiency. + */ +class SpatialFastFilter : public Filter { +public: + SpatialFastFilter(const std::string &activationKey = "") { + ob_error *error = nullptr; + auto impl = ob_create_private_filter("SpatialFastFilter", activationKey.c_str(), &error); + Error::handle(&error); + init(impl); + } + + virtual ~SpatialFastFilter() noexcept override = default; + + /** + * @brief Get the spatial fast filter radius range. + * + * @return OBIntPropertyRange the radius value of property range. + */ + OBIntPropertyRange getRadiusRange() { + OBIntPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "radius") == 0) { + range = getPropertyRange(item, getConfigValue("radius")); + break; + } + } + return range; + } + + /** + * @brief Get the spatial fast filter params. + * + * @return OBSpatialFastFilterParams + */ + OBSpatialFastFilterParams getFilterParams() { + OBSpatialFastFilterParams params{}; + params.radius = static_cast(getConfigValue("radius")); + return params; + } + + /** + * @brief Set the spatial fast filter params. + * + * @param params OBSpatialFastFilterParams. + */ + void setFilterParams(OBSpatialFastFilterParams params) { + setConfigValue("radius", params.radius); + } +}; + +/** + * @brief The Spatial Moderate Filter utilizes an optimized average smoothing algorithm, + * to achieve a balance between processing speed and the quality of smoothing achieved. + */ +class SpatialModerateFilter : public Filter { +public: + SpatialModerateFilter(const std::string &activationKey = "") { + ob_error *error = nullptr; + auto impl = ob_create_private_filter("SpatialModerateFilter", activationKey.c_str(), &error); + Error::handle(&error); + init(impl); + } + + virtual ~SpatialModerateFilter() noexcept override = default; + + /** + * @brief Get the spatial moderate filter magnitude range. + * + * @return OBIntPropertyRange the magnitude value of property range. + */ + OBIntPropertyRange getMagnitudeRange() { + OBIntPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "magnitude") == 0) { + range = getPropertyRange(item, getConfigValue("magnitude")); + break; + } + } + return range; + } + + /** + * @brief Get the spatial moderate filter radius range. + * + * @return OBIntPropertyRange the radius value of property range. + */ + OBIntPropertyRange getRadiusRange() { + OBIntPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "radius") == 0) { + range = getPropertyRange(item, getConfigValue("radius")); + break; + } + } + return range; + } + + /** + * @brief Get the spatial moderate filter disp diff range. + * + * @return OBIntPropertyRange the disp diff value of property range. + */ + OBIntPropertyRange getDispDiffRange() { + OBIntPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "disp_diff") == 0) { + range = getPropertyRange(item, getConfigValue("disp_diff")); + break; + } + } + return range; + } + + /** + * @brief Get the spatial moderate filter params. + * + * @return OBSpatialModerateFilterParams + */ + OBSpatialModerateFilterParams getFilterParams() { + OBSpatialModerateFilterParams params{}; + params.magnitude = static_cast(getConfigValue("magnitude")); + params.radius = static_cast(getConfigValue("radius")); + params.disp_diff = static_cast(getConfigValue("disp_diff")); + return params; + } + + /** + * @brief Set the spatial moderate filter params. + * + * @param params OBSpatialModerateFilterParams. + */ + void setFilterParams(OBSpatialModerateFilterParams params) { + setConfigValue("magnitude", params.magnitude); + setConfigValue("radius", params.radius); + setConfigValue("disp_diff", params.disp_diff); + } +}; + +/** + * @brief Hole filling filter,the processing performed depends on the selected hole filling mode. + */ +class HoleFillingFilter : public Filter { +public: + HoleFillingFilter(const std::string &activationKey = "") { + ob_error *error = nullptr; + auto impl = ob_create_private_filter("HoleFillingFilter", activationKey.c_str(), &error); + Error::handle(&error); + init(impl); + } + + ~HoleFillingFilter() noexcept override = default; + + /** + * @brief Set the HoleFillingFilter mode. + * + * @param mode OBHoleFillingMode, OB_HOLE_FILL_TOP,OB_HOLE_FILL_NEAREST or OB_HOLE_FILL_FAREST. + */ + void setFilterMode(OBHoleFillingMode mode) { + setConfigValue("hole_filling_mode", static_cast(mode)); + } + + /** + * @brief Get the HoleFillingFilter mode. + * + * @return OBHoleFillingMode + */ + OBHoleFillingMode getFilterMode() { + return static_cast(static_cast(getConfigValue("hole_filling_mode"))); + } +}; + +/** + * @brief The noise removal filter,removing scattering depth pixels. + */ +class NoiseRemovalFilter : public Filter { +public: + NoiseRemovalFilter(const std::string &activationKey = "") { + ob_error *error = nullptr; + auto impl = ob_create_private_filter("NoiseRemovalFilter", activationKey.c_str(), &error); + Error::handle(&error); + init(impl); + } + + ~NoiseRemovalFilter() noexcept override = default; + + /** + * @brief Set the noise removal filter params. + * + * @param[in] filterParams ob_noise_removal_filter_params. + */ + void setFilterParams(OBNoiseRemovalFilterParams filterParams) { + setConfigValue("max_size", static_cast(filterParams.max_size)); + setConfigValue("min_diff", static_cast(filterParams.disp_diff)); + // todo:set noise remove type + } + + /** + * @brief Get the noise removal filter params. + * + * @return OBNoiseRemovalFilterParams. + */ + OBNoiseRemovalFilterParams getFilterParams() { + OBNoiseRemovalFilterParams param{}; + param.max_size = static_cast(getConfigValue("max_size")); + param.disp_diff = static_cast(getConfigValue("min_diff")); + // todo: type is not set + return param; + } + + /** + * @brief Get the noise removal filter disp diff range. + * @return OBUint16PropertyRange The disp diff of property range. + */ + OBUint16PropertyRange getDispDiffRange() { + OBUint16PropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "min_diff") == 0) { + range = getPropertyRange(item, getConfigValue("min_diff")); + break; + } + } + return range; + } + + /** + * @brief Get the noise removal filter max size range. + * @return OBUint16PropertyRange The max size of property range. + */ + OBUint16PropertyRange getMaxSizeRange() { + OBUint16PropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "max_size") == 0) { + range = getPropertyRange(item, getConfigValue("max_size")); + break; + } + } + return range; + } +}; + +/** + * @brief Temporal filter + */ +class TemporalFilter : public Filter { +public: + TemporalFilter(const std::string &activationKey = "") { + ob_error *error = nullptr; + auto impl = ob_create_private_filter("TemporalFilter", activationKey.c_str(), &error); + Error::handle(&error); + init(impl); + } + + ~TemporalFilter() noexcept override = default; + + /** + * @brief Get the TemporalFilter diffscale range. + * + * @return OBFloatPropertyRange the diffscale value of property range. + */ + OBFloatPropertyRange getDiffScaleRange() { + OBFloatPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "diff_scale") == 0) { + range = getPropertyRange(item, getConfigValue("diff_scale")); + break; + } + } + return range; + } + + /** + * @brief Set the TemporalFilter diffscale value. + * + * @param value diffscale value. + */ + void setDiffScale(float value) { + setConfigValue("diff_scale", static_cast(value)); + } + + /** + * @brief Get the TemporalFilter weight range. + * + * @return OBFloatPropertyRange the weight value of property range. + */ + OBFloatPropertyRange getWeightRange() { + OBFloatPropertyRange range{}; + const auto &schemaVec = getConfigSchemaVec(); + for(const auto &item: schemaVec) { + if(strcmp(item.name, "weight") == 0) { + range = getPropertyRange(item, getConfigValue("weight")); + break; + } + } + return range; + } + + /** + * @brief Set the TemporalFilter weight value. + * + * @param value weight value. + */ + void setWeight(float value) { + setConfigValue("weight", static_cast(value)); + } +}; + +/** + * @brief Depth to disparity or disparity to depth + */ +class DisparityTransform : public Filter { +public: + DisparityTransform(const std::string &activationKey = "") { + ob_error *error = nullptr; + auto impl = ob_create_private_filter("DisparityTransform", activationKey.c_str(), &error); + Error::handle(&error); + init(impl); + } + + ~DisparityTransform() noexcept override = default; +}; + +class OBFilterList { +private: + ob_filter_list_t *impl_; + +public: + explicit OBFilterList(ob_filter_list_t *impl) : impl_(impl) {} + + ~OBFilterList() noexcept { + ob_error *error = nullptr; + ob_delete_filter_list(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get the number of filters + * + * @return uint32_t The number of filters + */ + uint32_t getCount() const { + ob_error *error = nullptr; + auto count = ob_filter_list_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Get the Filter object at the specified index + * + * @param index The filter index. The range is [0, count-1]. If the index exceeds the range, an exception will be thrown. + * @return std::shared_ptr The filter object. + */ + std::shared_ptr getFilter(uint32_t index) { + ob_error *error = nullptr; + auto filter = ob_filter_list_get_filter(impl_, index, &error); + Error::handle(&error); + return std::make_shared(filter); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t count() const { + return getCount(); + } +}; + +/** + * @brief Returns the mapping of filter type names to their corresponding type_index. + */ +inline const std::unordered_map &getFilterTypeMap() { + static const std::unordered_map filterTypeMap = { + { "PointCloudFilter", typeid(PointCloudFilter) }, { "Align", typeid(Align) }, + { "FormatConverter", typeid(FormatConvertFilter) }, { "HDRMerge", typeid(HdrMerge) }, + { "SequenceIdFilter", typeid(SequenceIdFilter) }, { "DecimationFilter", typeid(DecimationFilter) }, + { "ThresholdFilter", typeid(ThresholdFilter) }, { "SpatialAdvancedFilter", typeid(SpatialAdvancedFilter) }, + { "HoleFillingFilter", typeid(HoleFillingFilter) }, { "NoiseRemovalFilter", typeid(NoiseRemovalFilter) }, + { "TemporalFilter", typeid(TemporalFilter) }, { "DisparityTransform", typeid(DisparityTransform) }, + { "SpatialFastFilter", typeid(SpatialFastFilter) }, { "SpatialModerateFilter", typeid(SpatialModerateFilter) }, + }; + return filterTypeMap; +} + +/** + * @brief Define the is() template function for the Filter class + * + * @note When adding a new filter class, ensure the filter type map + * (see getFilterTypeMap()) is updated accordingly to maintain correct type matching. + */ +template bool Filter::is() { + std::string name = type(); + + const auto &filterTypeMap = getFilterTypeMap(); + auto it = filterTypeMap.find(name); + if(it != filterTypeMap.end()) { + return std::type_index(typeid(T)) == it->second; + } + return false; +} + +} // namespace ob diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Frame.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Frame.hpp new file mode 100644 index 0000000..27f52cf --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Frame.hpp @@ -0,0 +1,1094 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Frame.hpp + * @brief Frame related type, which is mainly used to obtain frame data and frame information. + * + */ +#pragma once + +#include "Types.hpp" +#include "libobsensor/h/Frame.h" +#include "libobsensor/hpp/Error.hpp" +#include "libobsensor/hpp/StreamProfile.hpp" +#include +#include +#include +#include + +/** + * Frame classis inheritance hierarchy: + * Frame + * | + * +-----------+----------+----------+-----------+ + * | | | | | + * VideoFrame PointsFrame AccelFrame GyroFrame FrameSet + * | + * +----+----------+-------------------+ + * | | | + * ColorFrame DepthFrame IRFrame + * | + * +-----+-----+ + * | | + * IRLeftFrame IRRightFrame + */ + +namespace ob { +class Device; +class Sensor; + +/** + * @brief Define the frame class, which is the base class of all frame types. + * + */ +class Frame : public std::enable_shared_from_this { +protected: + /** + * @brief The pointer to the internal (c api level) frame object. + */ + const ob_frame *impl_ = nullptr; + +public: + /** + * @brief Construct a new Frame object with a given pointer to the internal frame object. + * + * @attention After calling this constructor, the frame object will own the internal frame object, and the internal frame object will be deleted when the + * frame object is destroyed. + * @attention The internal frame object should not be deleted by the caller. + * + * @param impl The pointer to the internal frame object. + */ + explicit Frame(const ob_frame *impl) : impl_(impl) {} + + /** + * @brief Get the internal (impl) frame object + * + * @return const ob_frame* the pointer to the internal frame object. + */ + const ob_frame *getImpl() const { + return impl_; + } + + /** + * @brief Destroy the Frame object + */ + virtual ~Frame() noexcept { + if(impl_) { + ob_error *error = nullptr; + ob_delete_frame(impl_, &error); + Error::handle(&error, false); + impl_ = nullptr; + } + } + + /** + * @brief Get the type of frame. + * + * @return OBFrameType The type of frame. + */ + virtual OBFrameType getType() const { + ob_error *error = nullptr; + auto type = ob_frame_get_type(impl_, &error); + Error::handle(&error); + + return type; + } + + /** + * @brief Get the format of the frame. + * + * @return OBFormat The format of the frame. + */ + virtual OBFormat getFormat() const { + ob_error *error = nullptr; + auto format = ob_frame_get_format(impl_, &error); + Error::handle(&error); + + return format; + } + + /** + * @brief Get the sequence number of the frame. + * + * @note The sequence number for each frame is managed by the SDK. It increments by 1 for each frame on each stream. + * + * @return uint64_t The sequence number of the frame. + */ + virtual uint64_t getIndex() const { + ob_error *error = nullptr; + auto index = ob_frame_get_index(impl_, &error); + Error::handle(&error); + + return index; + } + + /** + * @brief Get frame data + * + * @return const uint8_t * The frame data pointer. + */ + virtual uint8_t *getData() const { + ob_error *error = nullptr; + auto data = ob_frame_get_data(impl_, &error); + Error::handle(&error); + + return data; + } + + /** + * @brief Get the size of the frame data. + * + * @return uint32_t The size of the frame data. + * For point cloud data, this returns the number of bytes occupied by all point sets. To find the number of points, divide the dataSize by the structure + * size of the corresponding point type. + */ + virtual uint32_t getDataSize() const { + ob_error *error = nullptr; + auto dataSize = ob_frame_get_data_size(impl_, &error); + Error::handle(&error); + + return dataSize; + } + + /** + * @brief Get the hardware timestamp of the frame in microseconds. + * @brief The hardware timestamp is the time point when the frame was captured by the device, on device clock domain. + * + * @return uint64_t The hardware timestamp of the frame in microseconds. + */ + uint64_t getTimeStampUs() const { + ob_error *error = nullptr; + auto timeStampUs = ob_frame_get_timestamp_us(impl_, &error); + Error::handle(&error); + + return timeStampUs; + } + + /** + * @brief Get the system timestamp of the frame in microseconds. + * @brief The system timestamp is the time point when the frame was received by the host, on host clock domain. + * + * @return uint64_t The system timestamp of the frame in microseconds. + */ + uint64_t getSystemTimeStampUs() const { + ob_error *error = nullptr; + auto systemTimeStampUs = ob_frame_get_system_timestamp_us(impl_, &error); + Error::handle(&error); + + return systemTimeStampUs; + } + + /** + * @brief Get the global timestamp of the frame in microseconds. + * @brief The global timestamp is the time point when the frame was captured by the device, and has been converted to the host clock domain. The + * conversion process base on the device timestamp and can eliminate the timer drift of the device + * + * @attention The global timestamp disable by default. If global timestamp is not enabled, the function will return 0. To enable the global timestamp, + * please call @ref Device::enableGlobalTimestamp() function. + * @attention Only some devices support getting the global timestamp. Check the device support status by @ref Device::isGlobalTimestampSupported() function. + * + * @return uint64_t The global timestamp of the frame in microseconds. + */ + uint64_t getGlobalTimeStampUs() const { + ob_error *error = nullptr; + auto globalTimeStampUs = ob_frame_get_global_timestamp_us(impl_, &error); + Error::handle(&error); + + return globalTimeStampUs; + } + + /** + * @brief Get the metadata pointer of the frame. + * + * @return const uint8_t * The metadata pointer of the frame. + */ + uint8_t *getMetadata() const { + ob_error *error = nullptr; + auto metadata = ob_frame_get_metadata(impl_, &error); + Error::handle(&error); + + return metadata; + } + + /** + * @brief Get the size of the metadata of the frame. + * + * @return uint32_t The size of the metadata of the frame. + */ + uint32_t getMetadataSize() const { + ob_error *error = nullptr; + auto metadataSize = ob_frame_get_metadata_size(impl_, &error); + Error::handle(&error); + + return metadataSize; + } + + /** + * @brief Check if the frame object has metadata of a given type. + * + * @param type The metadata type. refer to @ref OBFrameMetadataType + * @return bool The result. + */ + bool hasMetadata(OBFrameMetadataType type) const { + ob_error *error = nullptr; + auto result = ob_frame_has_metadata(impl_, type, &error); + Error::handle(&error); + + return result; + } + + /** + * @brief Get the metadata value + * + * @param type The metadata type. refer to @ref OBFrameMetadataType + * @return int64_t The metadata value. + */ + int64_t getMetadataValue(OBFrameMetadataType type) const { + ob_error *error = nullptr; + auto value = ob_frame_get_metadata_value(impl_, type, &error); + Error::handle(&error); + + return value; + } + + /** + * @brief get StreamProfile of the frame + * + * @return std::shared_ptr The StreamProfile of the frame, may return nullptr if the frame is not captured from a stream. + */ + std::shared_ptr getStreamProfile() const { + ob_error *error = nullptr; + auto profile = ob_frame_get_stream_profile(impl_, &error); + Error::handle(&error); + return StreamProfileFactory::create(profile); + } + + /** + * @brief get owner sensor of the frame + * + * @return std::shared_ptr The owner sensor of the frame, return nullptr if the frame is not owned by any sensor or the sensor is destroyed + */ + std::shared_ptr getSensor() const { + ob_error *error = nullptr; + auto sensor = ob_frame_get_sensor(impl_, &error); + Error::handle(&error); + + return std::make_shared(sensor); + } + + /** + * @brief get owner device of the frame + * + * @return std::shared_ptr The owner device of the frame, return nullptr if the frame is not owned by any device or the device is destroyed + */ + std::shared_ptr getDevice() const { + ob_error *error = nullptr; + auto device = ob_frame_get_device(impl_, &error); + Error::handle(&error); + + return std::make_shared(device); + } + + /** + * @brief Check if the runtime type of the frame object is compatible with a given type. + * + * @tparam T The given type. + * @return bool The result. + */ + template bool is() const; + + /** + * @brief Convert the frame object to a target type. + * + * @tparam T The target type. + * @return std::shared_ptr The result. If it cannot be converted, an exception will be thrown. + */ + template std::shared_ptr as() { + if(!is()) { + throw std::runtime_error("unsupported operation, object's type is not require type"); + } + + ob_error *error = nullptr; + ob_frame_add_ref(impl_, &error); + Error::handle(&error); + + return std::make_shared(impl_); + } + + /** + * @brief Convert the frame object to a target type. + * + * @tparam T The target type. + * @return std::shared_ptr The result. If it cannot be converted, an exception will be thrown. + */ + template std::shared_ptr as() const { + if(!is()) { + throw std::runtime_error("unsupported operation, object's type is not require type"); + } + + ob_error *error = nullptr; + ob_frame_add_ref(impl_, &error); + Error::handle(&error); + + return std::make_shared(impl_); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + OBFrameType type() const { + return getType(); + } + + virtual OBFormat format() const { + return getFormat(); + } + + virtual uint64_t index() const { + return getIndex(); + } + + virtual void *data() const { + auto data = getData(); + return reinterpret_cast(data); + } + + virtual uint32_t dataSize() const { + return getDataSize(); + } + + uint64_t timeStamp() const { + return getTimeStampUs() / 1000; + } + + uint64_t timeStampUs() const { + return getTimeStampUs(); + } + + uint64_t systemTimeStamp() const { + return getSystemTimeStampUs() / 1000; + } + + uint64_t systemTimeStampUs() const { + return getSystemTimeStampUs(); + } + + uint64_t globalTimeStampUs() const { + return getGlobalTimeStampUs(); + } + + uint8_t *metadata() const { + return getMetadata(); + } + + uint32_t metadataSize() const { + return getMetadataSize(); + } +}; + +/** + * @brief Define the VideoFrame class, which inherits from the Frame class + */ +class VideoFrame : public Frame { +public: + /** + * @brief Construct a new VideoFrame object with a given pointer to the internal frame object. + * + * @attention After calling this constructor, the frame object will own the internal frame object, and the internal frame object will be deleted when the + * frame object is destroyed. + * @attention The internal frame object should not be deleted by the caller. + * + * @param impl The pointer to the internal frame object. + */ + explicit VideoFrame(const ob_frame *impl) : Frame(impl) {}; + + ~VideoFrame() noexcept override = default; + + /** + * @brief Get the width of the frame. + * + * @return uint32_t The width of the frame. + */ + uint32_t getWidth() const { + ob_error *error = nullptr; + auto width = ob_video_frame_get_width(impl_, &error); + Error::handle(&error); + + return width; + } + + /** + * @brief Get the height of the frame. + * + * @return uint32_t The height of the frame. + */ + uint32_t getHeight() const { + ob_error *error = nullptr; + auto height = ob_video_frame_get_height(impl_, &error); + Error::handle(&error); + + return height; + } + + /** + * @brief Get the Pixel Type object + * @brief Usually used to determine the pixel type of depth frame (depth, disparity, raw phase, etc.) + * + * @attention Always return OB_PIXEL_UNKNOWN for non-depth frame currently + * + * @return OBPixelType + */ + OBPixelType getPixelType() const { + ob_error *error = nullptr; + auto pixelType = ob_video_frame_get_pixel_type(impl_, &error); + Error::handle(&error); + + return pixelType; + } + + /** + * @brief Get the effective number of pixels in the frame. + * @attention Only valid for Y8/Y10/Y11/Y12/Y14/Y16 format. + * + * @return uint8_t The effective number of pixels in the frame, or 0 if it is an unsupported format. + */ + uint8_t getPixelAvailableBitSize() const { + ob_error *error = nullptr; + auto bitSize = ob_video_frame_get_pixel_available_bit_size(impl_, &error); + Error::handle(&error); + + return bitSize; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t width() const { + return getWidth(); + } + + uint32_t height() const { + return getHeight(); + } + + uint8_t pixelAvailableBitSize() const { + return getPixelAvailableBitSize(); + } +}; + +/** + * @brief Define the ColorFrame class, which inherits from the VideoFrame classd + */ +class ColorFrame : public VideoFrame { +public: + /** + * @brief Construct a new ColorFrame object with a given pointer to the internal frame object. + * + * @attention After calling this constructor, the frame object will own the internal frame object, and the internal frame object will be deleted when the + * frame object is destroyed. + * @attention The internal frame object should not be deleted by the caller. + * @attention Please use the FrameFactory to create a Frame object. + * + * @param impl The pointer to the internal frame object. + */ + explicit ColorFrame(const ob_frame *impl) : VideoFrame(impl) {}; + + ~ColorFrame() noexcept override = default; +}; + +/** + * @brief Define the DepthFrame class, which inherits from the VideoFrame class + */ +class DepthFrame : public VideoFrame { + +public: + /** + * @brief Construct a new DepthFrame object with a given pointer to the internal frame object. + * + * @attention After calling this constructor, the frame object will own the internal frame object, and the internal frame object will be deleted when the + * frame object is destroyed. + * @attention The internal frame object should not be deleted by the caller. + * @attention Please use the FrameFactory to create a Frame object. + * + * @param impl The pointer to the internal frame object. + */ + explicit DepthFrame(const ob_frame *impl) : VideoFrame(impl) {}; + + ~DepthFrame() noexcept override = default; + + /** + * @brief Get the value scale of the depth frame. The pixel value of depth frame is multiplied by the scale to give a depth value in millimeters. + * For example, if valueScale=0.1 and a certain coordinate pixel value is pixelValue=10000, then the depth value = pixelValue*valueScale = + * 10000*0.1=1000mm. + * + * @return float The scale. + */ + float getValueScale() const { + ob_error *error = nullptr; + auto scale = ob_depth_frame_get_value_scale(impl_, &error); + Error::handle(&error); + + return scale; + } +}; + +/** + * @brief Define the IRFrame class, which inherits from the VideoFrame class + * + */ +class IRFrame : public VideoFrame { + +public: + /** + * @brief Construct a new IRFrame object with a given pointer to the internal frame object. + * + * @attention After calling this constructor, the frame object will own the internal frame object, and the internal frame object will be deleted when the + * frame object is destroyed. + * @attention The internal frame object should not be deleted by the caller. + * @attention Please use the FrameFactory to create a Frame object. + * + * @param impl The pointer to the internal frame object. + */ + explicit IRFrame(const ob_frame *impl) : VideoFrame(impl) {}; + + ~IRFrame() noexcept override = default; +}; + +/** + * @brief Define the ConfidenceFrame class, which inherits from the VideoFrame class + * + */ +class ConfidenceFrame : public VideoFrame { + +public: + /** + * @brief Construct a new ConfidenceFrame object with a given pointer to the internal frame object. + * + * @attention After calling this constructor, the frame object will own the internal frame object, and the internal frame object will be deleted when the + * frame object is destroyed. + * @attention The internal frame object should not be deleted by the caller. + * @attention Please use the FrameFactory to create a Frame object. + * + * @param impl The pointer to the internal frame object. + */ + explicit ConfidenceFrame(const ob_frame *impl) : VideoFrame(impl) {}; + + ~ConfidenceFrame() noexcept override = default; +}; + +/** + * @brief Define the PointsFrame class, which inherits from the Frame class + * @brief The PointsFrame class is used to obtain pointcloud data and point cloud information. + * + * @note The pointcloud data format can be obtained from the @ref Frame::getFormat() function. Witch can be one of the following formats: + * - @ref OB_FORMAT_POINT: 32-bit float format with 3D point coordinates (x, y, z), @ref OBPoint + * - @ref OB_FORMAT_RGB_POINT: 32-bit float format with 3D point coordinates (x, y, z) and point colors (r, g, b) @ref, OBColorPoint + */ +class PointsFrame : public Frame { + +public: + /** + * @brief Construct a new PointsFrame object with a given pointer to the internal frame object. + * + * @attention After calling this constructor, the frame object will own the internal frame object, and the internal frame object will be deleted when the + * frame object is destroyed. + * @attention The internal frame object should not be deleted by the caller. + * @attention Please use the FrameFactory to create a Frame object. + * + * @param impl The pointer to the internal frame object. + */ + explicit PointsFrame(const ob_frame *impl) : Frame(impl) {}; + + ~PointsFrame() noexcept override = default; + + /** + * @brief Get the point coordinate value scale of the points frame. The point position value of the points frame is multiplied by the scale to give a + * position value in millimeters. For example, if scale=0.1, the x-coordinate value of a point is x = 10000, which means that the actual x-coordinate value + * = x*scale = 10000*0.1 = 1000mm. + * + * @return float The coordinate value scale. + */ + float getCoordinateValueScale() const { + ob_error *error = nullptr; + auto scale = ob_points_frame_get_coordinate_value_scale(impl_, &error); + Error::handle(&error); + + return scale; + } + + /** + * @brief Get the width of the frame. + * + * @return uint32_t The width of the frame. + */ + uint32_t getWidth() const { + ob_error *error = nullptr; + // TODO + auto width = ob_point_cloud_frame_get_width(impl_, &error); + Error::handle(&error); + + return width; + } + + /** + * @brief Get the height of the frame. + * + * @return uint32_t The height of the frame. + */ + uint32_t getHeight() const { + ob_error *error = nullptr; + auto height = ob_point_cloud_frame_get_height(impl_, &error); + Error::handle(&error); + + return height; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. +#define getPositionValueScale getCoordinateValueScale +}; + +/** + * @brief Define the AccelFrame class, which inherits from the Frame class + * + */ +class AccelFrame : public Frame { + +public: + explicit AccelFrame(const ob_frame *impl) : Frame(impl) {}; + + ~AccelFrame() noexcept override = default; + + /** + * @brief Get the accelerometer frame data + * + * @return OBAccelValue The accelerometer frame data + */ + OBAccelValue getValue() const { + ob_error *error = nullptr; + auto value = ob_accel_frame_get_value(impl_, &error); + Error::handle(&error); + + return value; + } + + /** + * @brief Get the temperature when the frame was sampled + * + * @return float The temperature value in celsius + */ + float getTemperature() const { + ob_error *error = nullptr; + auto temp = ob_accel_frame_get_temperature(impl_, &error); + Error::handle(&error); + + return temp; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + OBAccelValue value() { + return getValue(); + } + + float temperature() { + return getTemperature(); + } +}; + +/** + * @brief Define the GyroFrame class, which inherits from the Frame class + */ +class GyroFrame : public Frame { + +public: + explicit GyroFrame(const ob_frame *impl) : Frame(impl) {}; + + ~GyroFrame() noexcept override = default; + + /** + * @brief Get the gyro frame data + * + * @return OBAccelValue The gyro frame data + */ + OBGyroValue getValue() const { + ob_error *error = nullptr; + auto value = ob_gyro_frame_get_value(impl_, &error); + Error::handle(&error); + + return value; + } + + /** + * @brief Get the temperature when the frame was sampled + * + * @return float The temperature value in celsius + */ + float getTemperature() const { + ob_error *error = nullptr; + auto temperature = ob_gyro_frame_get_temperature(impl_, &error); + Error::handle(&error); + + return temperature; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + OBGyroValue value() { + return getValue(); + } + + float temperature() { + return getTemperature(); + } +}; + +/** + * @brief Define the FrameSet class, which inherits from the Frame class + * @brief A FrameSet is a container for multiple frames of different types. + */ +class FrameSet : public Frame { + +public: + explicit FrameSet(const ob_frame *impl) : Frame(impl) {}; + + ~FrameSet() noexcept override = default; + + /** + * @brief Get the number of frames in the FrameSet + * + * @return uint32_t The number of frames + */ + uint32_t getCount() const { + ob_error *error = nullptr; + auto count = ob_frameset_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Get a frame of a specific type from the FrameSet + * + * @param frameType The type of sensor + * @return std::shared_ptr The corresponding type of frame + */ + std::shared_ptr getFrame(OBFrameType frameType) const { + ob_error *error = nullptr; + auto frame = ob_frameset_get_frame(impl_, frameType, &error); + if(!frame) { + return nullptr; + } + Error::handle(&error); + return std::make_shared(frame); + } + + /** + * @brief Get a frame at a specific index from the FrameSet + * + * @param index The index of the frame + * @return std::shared_ptr The frame at the specified index + */ + std::shared_ptr getFrameByIndex(uint32_t index) const { + ob_error *error = nullptr; + auto frame = ob_frameset_get_frame_by_index(impl_, index, &error); + if(!frame) { + return nullptr; + } + Error::handle(&error); + return std::make_shared(frame); + } + + /** + * @brief Push a frame to the FrameSet + * + * @attention If the FrameSet contains the same type of frame, the new frame will replace the old one. + * + * @param frame The frame to be pushed + */ + void pushFrame(std::shared_ptr frame) const { + ob_error *error = nullptr; + + // unsafe operation, need to cast const to non-const + auto unConstImpl = const_cast(impl_); + + auto otherImpl = frame->getImpl(); + ob_frameset_push_frame(unConstImpl, otherImpl, &error); + + Error::handle(&error); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t frameCount() const { + return getCount(); + } + + std::shared_ptr depthFrame() const { + auto frame = getFrame(OB_FRAME_DEPTH); + if(frame == nullptr) { + return nullptr; + } + auto depthFrame = frame->as(); + return depthFrame; + } + + std::shared_ptr colorFrame() const { + auto frame = getFrame(OB_FRAME_COLOR); + if(frame == nullptr) { + return nullptr; + } + auto colorFrame = frame->as(); + return colorFrame; + } + + std::shared_ptr irFrame() const { + auto frame = getFrame(OB_FRAME_IR); + if(frame == nullptr) { + return nullptr; + } + auto irFrame = frame->as(); + return irFrame; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + std::shared_ptr pointsFrame() const { + auto frame = getFrame(OB_FRAME_POINTS); + if(frame == nullptr) { + return nullptr; + } + auto pointsFrame = frame->as(); + return pointsFrame; + } + + std::shared_ptr getFrame(int index) const { + return getFrameByIndex(index); + } +}; + +/** + * @brief FrameFactory class, which provides some static functions to create frame objects + */ +class FrameFactory { +public: + /** + * @brief Create a Frame object of a specific type with a given format and data size. + * + * @param frameType The type of the frame. + * @param format The format of the frame. + * @param dataSize The size of the data in bytes. + * @return std::shared_ptr The created frame object. + */ + static std::shared_ptr createFrame(OBFrameType frameType, OBFormat format, uint32_t dataSize) { + ob_error *error = nullptr; + auto impl = ob_create_frame(frameType, format, dataSize, &error); + Error::handle(&error); + + return std::make_shared(impl); + } + + /** + * @brief Create a VideoFrame object of a specific type with a given format, width, height, and stride. + * @note If stride is not specified, it will be calculated based on the width and format. + * + * @param frameType The type of the frame. + * @param format The format of the frame. + * @param width The width of the frame. + * @param height The height of the frame. + * @param stride The stride of the frame. + * + * @return std::shared_ptr The created video frame object. + */ + static std::shared_ptr createVideoFrame(OBFrameType frameType, OBFormat format, uint32_t width, uint32_t height, uint32_t stride = 0) { + ob_error *error = nullptr; + auto impl = ob_create_video_frame(frameType, format, width, height, stride, &error); + Error::handle(&error); + + auto frame = std::make_shared(impl); + return frame->as(); + } + + /** + * @brief Create (clone) a frame object based on the specified other frame object. + * @brief The new frame object will have the same properties as the other frame object, but the data buffer is newly allocated. + * + * @param shouldCopyData If true, the data of the source frame object will be copied to the new frame object. If false, the new frame object will + * have a data buffer with random data. The default value is true. + * + * @return std::shared_ptr The new frame object. + */ + static std::shared_ptr createFrameFromOtherFrame(std::shared_ptr otherFrame, bool shouldCopyData = true) { + ob_error *error = nullptr; + auto otherImpl = otherFrame->getImpl(); + auto impl = ob_create_frame_from_other_frame(otherImpl, shouldCopyData, &error); + Error::handle(&error); + + return std::make_shared(impl); + } + + /** + * @brief Create a Frame From (according to)Stream Profile object + * + * @param profile The stream profile object to create the frame from. + * + * @return std::shared_ptr The created frame object. + */ + static std::shared_ptr createFrameFromStreamProfile(std::shared_ptr profile) { + ob_error *error = nullptr; + auto impl = ob_create_frame_from_stream_profile(profile->getImpl(), &error); + Error::handle(&error); + + return std::make_shared(impl); + } + + /** + * @brief The callback function to destroy the buffer when the frame is destroyed. + */ + typedef std::function BufferDestroyCallback; + + /** + * @brief Create a frame object based on an externally created buffer. + * + * @attention The buffer is owned by the caller, and will not be destroyed by the frame object. The user should ensure that the buffer is valid and not + * modified. + * + * @param[in] frameType Frame object type. + * @param[in] format Frame object format. + * @param[in] buffer Frame object buffer. + * @param[in] destroyCallback Destroy callback, will be called when the frame object is destroyed. + * @param[in] bufferSize Frame object buffer size. + * + * @return std::shared_ptr The created frame object. + */ + static std::shared_ptr createFrameFromBuffer(OBFrameType frameType, OBFormat format, uint8_t *buffer, BufferDestroyCallback destroyCallback, + uint32_t bufferSize) { + ob_error *error = nullptr; + auto ctx = new BufferDestroyContext{ destroyCallback }; + auto impl = ob_create_frame_from_buffer(frameType, format, buffer, bufferSize, &FrameFactory::BufferDestroy, ctx, &error); + Error::handle(&error); + + return std::make_shared(impl); + } + + /** + * @brief Create a video frame object based on an externally created buffer. + * + * @attention The buffer is owned by the user and will not be destroyed by the frame object. The user should ensure that the buffer is valid and not + * modified. + * @attention The frame object is created with a reference count of 1, and the reference count should be decreased by calling @ref ob_delete_frame() when it + * is no longer needed. + * + * @param[in] frameType Frame object type. + * @param[in] format Frame object format. + * @param[in] width Frame object width. + * @param[in] height Frame object height. + * @param[in] buffer Frame object buffer. + * @param[in] bufferSize Frame object buffer size. + * @param[in] destroyCallback Destroy callback, will be called when the frame object is destroyed. + * @param[in] stride Row span in bytes. If 0, the stride is calculated based on the width and format. + * + * @return std::shared_ptr The created video frame object. + */ + static std::shared_ptr createVideoFrameFromBuffer(OBFrameType frameType, OBFormat format, uint32_t width, uint32_t height, uint8_t *buffer, + BufferDestroyCallback destroyCallback, uint32_t bufferSize, uint32_t stride = 0) { + ob_error *error = nullptr; + auto ctx = new BufferDestroyContext{ destroyCallback }; + auto impl = ob_create_video_frame_from_buffer(frameType, format, width, height, stride, buffer, bufferSize, &FrameFactory::BufferDestroy, ctx, &error); + Error::handle(&error); + + auto frame = std::make_shared(impl); + return frame->as(); + } + + /** + * @brief Create a new FrameSet object. + * + * This function creates a new FrameSet instance by internally calling the native C API. + * The returned FrameSet is managed by a std::shared_ptr, and its lifetime will be + * automatically managed. When no references remain, the underlying native resources will be released. + * + * @return std::shared_ptr The created FrameSet object. + */ + static std::shared_ptr createFrameSet() { + ob_error *error = nullptr; + auto impl = ob_create_frameset(&error); + Error::handle(&error); + return std::make_shared(impl); + } + +private: + struct BufferDestroyContext { + BufferDestroyCallback callback; + }; + + static void BufferDestroy(uint8_t *buffer, void *context) { + auto *ctx = static_cast(context); + if(ctx->callback) { + ctx->callback(buffer); + } + delete ctx; + } +}; + +/** + * @brief FrameHepler class, which provides some static functions to set timestamp for frame objects + * FrameHepler inherited from the FrameFactory and the timestamp interface implement here both for compatibility purposes. + */ +class FrameHelper : public FrameFactory { +public: + /** + * @brief Set the device timestamp of the frame. + * + * @param frame The frame object. + * @param deviceTimestampUs The device timestamp to set in microseconds. + */ + static void setFrameDeviceTimestampUs(std::shared_ptr frame, uint64_t deviceTimestampUs) { + ob_error *error = nullptr; + auto impl = const_cast(frame->getImpl()); + ob_frame_set_timestamp_us(impl, deviceTimestampUs, &error); + Error::handle(&error); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + static void setFrameSystemTimestamp(std::shared_ptr frame, uint64_t systemTimestamp) { + // In order to compile, some high-version compilers will warn that the function parameters are not used. + (void)frame; + (void)systemTimestamp; + } + + static void setFrameDeviceTimestamp(std::shared_ptr frame, uint64_t deviceTimestamp) { + // In order to compile, some high-version compilers will warn that the function parameters are not used. + (void)frame; + (void)deviceTimestamp; + } +}; + +// Define the is() template function for the Frame class +template bool Frame::is() const { + switch(this->getType()) { + case OB_FRAME_IR_LEFT: // Follow + case OB_FRAME_IR_RIGHT: // Follow + case OB_FRAME_IR: + return (typeid(T) == typeid(IRFrame) || typeid(T) == typeid(VideoFrame)); + case OB_FRAME_DEPTH: + return (typeid(T) == typeid(DepthFrame) || typeid(T) == typeid(VideoFrame)); + case OB_FRAME_COLOR: + return (typeid(T) == typeid(ColorFrame) || typeid(T) == typeid(VideoFrame)); + case OB_FRAME_CONFIDENCE: + return (typeid(T) == typeid(ConfidenceFrame) || typeid(T) == typeid(VideoFrame)); + case OB_FRAME_GYRO: + return (typeid(T) == typeid(GyroFrame)); + case OB_FRAME_ACCEL: + return (typeid(T) == typeid(AccelFrame)); + case OB_FRAME_POINTS: + return (typeid(T) == typeid(PointsFrame)); + case OB_FRAME_SET: + return (typeid(T) == typeid(FrameSet)); + default: + std::cout << "ob::Frame::is() did not catch frame type: " << (int)this->getType() << std::endl; + break; + } + return false; +} + +} // namespace ob diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Pipeline.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Pipeline.hpp new file mode 100644 index 0000000..61009f1 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Pipeline.hpp @@ -0,0 +1,451 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Pipeline.hpp + * @brief The SDK's advanced API type can quickly implement switching streaming and frame synchronization + * operations. + */ +#pragma once + +#include "Frame.hpp" +#include "Device.hpp" +#include "StreamProfile.hpp" + +#include "libobsensor/h/Pipeline.h" +#include "libobsensor/hpp/Types.hpp" +#include "libobsensor/hpp/TypeHelper.hpp" + +#include +#include +namespace ob { + +/** + * @brief Config class for configuring pipeline parameters + * + * The Config class provides an interface for configuring pipeline parameters. + */ +class Config { +private: + ob_config_t *impl_; + +public: + /** + * @brief Construct a new Config object + */ + Config() { + ob_error *error = nullptr; + impl_ = ob_create_config(&error); + Error::handle(&error); + } + + explicit Config(ob_config_t *impl) : impl_(impl) {} + + /** + * @brief Destroy the Config object + */ + ~Config() noexcept { + ob_error *error = nullptr; + ob_delete_config(impl_, &error); + Error::handle(&error, false); + } + + ob_config_t *getImpl() const { + return impl_; + } + + /** + * @brief enable a stream with a specific stream type + * + * @param streamType The stream type to be enabled + */ + void enableStream(OBStreamType streamType) const { + ob_error *error = nullptr; + ob_config_enable_stream(impl_, streamType, &error); + Error::handle(&error); + } + + /** + * @brief Enable a stream with a specific sensor type + * @brief Will convert sensor type to stream type automatically. + * + * @param sensorType The sensor type to be enabled + */ + void enableStream(OBSensorType sensorType) const { + auto streamType = ob::TypeHelper::convertSensorTypeToStreamType(sensorType); + enableStream(streamType); + } + + /** + * @brief Enable a stream to be used in the pipeline + * + * @param streamProfile The stream configuration to be enabled + */ + void enableStream(std::shared_ptr streamProfile) const { + ob_error *error = nullptr; + auto c_stream_profile = streamProfile->getImpl(); + ob_config_enable_stream_with_stream_profile(impl_, c_stream_profile, &error); + Error::handle(&error); + } + + /** + * @brief Enable a video stream to be used in the pipeline. + * + * This function allows users to enable a video stream with customizable parameters. + * If no parameters are specified, the stream will be enabled with default resolution settings. + * Users who wish to set custom resolutions should refer to the product manual, as available resolutions vary by camera model. + * + * @param streamType The video stream type. + * @param width The video stream width (default is OB_WIDTH_ANY, which selects the default resolution). + * @param height The video stream height (default is OB_HEIGHT_ANY, which selects the default resolution). + * @param fps The video stream frame rate (default is OB_FPS_ANY, which selects the default frame rate). + * @param format The video stream format (default is OB_FORMAT_ANY, which selects the default format). + */ + void enableVideoStream(OBStreamType streamType, uint32_t width = OB_WIDTH_ANY, uint32_t height = OB_HEIGHT_ANY, uint32_t fps = OB_FPS_ANY, + OBFormat format = OB_FORMAT_ANY) const { + ob_error *error = nullptr; + ob_config_enable_video_stream(impl_, streamType, width, height, fps, format, &error); + Error::handle(&error); + } + + /** + * @brief Enable a video stream to be used in the pipeline. + * @brief Will convert sensor type to stream type automatically. + * + * @param sensorType The sensor type to be enabled. + * @param width The video stream width (default is OB_WIDTH_ANY, which selects the default resolution). + * @param height The video stream height (default is OB_HEIGHT_ANY, which selects the default resolution). + * @param fps The video stream frame rate (default is OB_FPS_ANY, which selects the default frame rate). + * @param format The video stream format (default is OB_FORMAT_ANY, which selects the default format). + */ + void enableVideoStream(OBSensorType sensorType, uint32_t width = OB_WIDTH_ANY, uint32_t height = OB_HEIGHT_ANY, uint32_t fps = OB_FPS_ANY, + OBFormat format = OB_FORMAT_ANY) const { + auto streamType = ob::TypeHelper::convertSensorTypeToStreamType(sensorType); + enableVideoStream(streamType, width, height, fps, format); + } + + /** + * @brief Enable an accelerometer stream to be used in the pipeline. + * + * This function allows users to enable an accelerometer stream with customizable parameters. + * If no parameters are specified, the stream will be enabled with default settings. + * Users who wish to set custom full-scale ranges or sample rates should refer to the product manual, as available settings vary by device model. + * + * @param fullScaleRange The full-scale range of the accelerometer (default is OB_ACCEL_FULL_SCALE_RANGE_ANY, which selects the default range). + * @param sampleRate The sample rate of the accelerometer (default is OB_ACCEL_SAMPLE_RATE_ANY, which selects the default rate). + */ + void enableAccelStream(OBAccelFullScaleRange fullScaleRange = OB_ACCEL_FULL_SCALE_RANGE_ANY, + OBAccelSampleRate sampleRate = OB_ACCEL_SAMPLE_RATE_ANY) const { + ob_error *error = nullptr; + ob_config_enable_accel_stream(impl_, fullScaleRange, sampleRate, &error); + Error::handle(&error); + } + + /** + * @brief Enable a gyroscope stream to be used in the pipeline. + * + * This function allows users to enable a gyroscope stream with customizable parameters. + * If no parameters are specified, the stream will be enabled with default settings. + * Users who wish to set custom full-scale ranges or sample rates should refer to the product manual, as available settings vary by device model. + * + * @param fullScaleRange The full-scale range of the gyroscope (default is OB_GYRO_FULL_SCALE_RANGE_ANY, which selects the default range). + * @param sampleRate The sample rate of the gyroscope (default is OB_GYRO_SAMPLE_RATE_ANY, which selects the default rate). + */ + void enableGyroStream(OBGyroFullScaleRange fullScaleRange = OB_GYRO_FULL_SCALE_RANGE_ANY, OBGyroSampleRate sampleRate = OB_GYRO_SAMPLE_RATE_ANY) const { + ob_error *error = nullptr; + ob_config_enable_gyro_stream(impl_, fullScaleRange, sampleRate, &error); + Error::handle(&error); + } + + /** + * @deprecated Use enableStream(std::shared_ptr streamProfile) instead + * @brief Enable all streams to be used in the pipeline + */ + void enableAllStream() { + ob_error *error = nullptr; + ob_config_enable_all_stream(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Disable a stream to be used in the pipeline + * + * @param streamType The stream configuration to be disabled + */ + void disableStream(OBStreamType streamType) const { + ob_error *error = nullptr; + ob_config_disable_stream(impl_, streamType, &error); + Error::handle(&error); + } + + /** + * @brief Disable a sensor stream to be used in the pipeline. + * @brief Will convert sensor type to stream type automatically. + * + * @param sensorType The sensor configuration to be disabled + */ + void disableStream(OBSensorType sensorType) const { + auto streamType = ob::TypeHelper::convertSensorTypeToStreamType(sensorType); + disableStream(streamType); + } + + /** + * @brief Disable all streams to be used in the pipeline + */ + void disableAllStream() const { + ob_error *error = nullptr; + ob_config_disable_all_stream(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Get the Enabled Stream Profile List + * + * @return std::shared_ptr + */ + std::shared_ptr getEnabledStreamProfileList() const { + ob_error *error = nullptr; + auto list = ob_config_get_enabled_stream_profile_list(impl_, &error); + Error::handle(&error); + return std::make_shared(list); + } + + /** + * @brief Set the alignment mode + * + * @param mode The alignment mode + */ + void setAlignMode(OBAlignMode mode) const { + ob_error *error = nullptr; + ob_config_set_align_mode(impl_, mode, &error); + Error::handle(&error); + } + + /** + * @brief Set whether the depth needs to be scaled after setting D2C + * + * @param enable Whether scaling is required + */ + void setDepthScaleRequire(bool enable) const { + ob_error *error = nullptr; + ob_config_set_depth_scale_after_align_require(impl_, enable, &error); + Error::handle(&error); + } + + /** + * @brief Set the frame aggregation output mode for the pipeline configuration + * @brief The processing strategy when the FrameSet generated by the frame aggregation function does not contain the frames of all opened streams (which + * can be caused by different frame rates of each stream, or by the loss of frames of one stream): drop directly or output to the user. + * + * @param mode The frame aggregation output mode to be set (default mode is @ref OB_FRAME_AGGREGATE_OUTPUT_ANY_SITUATION) + */ + void setFrameAggregateOutputMode(OBFrameAggregateOutputMode mode) const { + ob_error *error = nullptr; + ob_config_set_frame_aggregate_output_mode(impl_, mode, &error); + Error::handle(&error); + } +}; + +class Pipeline { +public: + /** + * @brief FrameSetCallback is a callback function type for frameset data arrival. + * + * @param frame The returned frameset data + */ + typedef std::function frame)> FrameSetCallback; + +private: + ob_pipeline_t *impl_; + FrameSetCallback callback_; + +public: + /** + * @brief Pipeline is a high-level interface for applications, algorithms related RGBD data streams. Pipeline can provide alignment inside and synchronized + * FrameSet. Pipeline() no parameter version, which opens the first device in the list of devices connected to the OS by default. If the application has + * obtained the device through the DeviceList, opening the Pipeline() at this time will throw an exception that the device has been created. + */ + Pipeline() { + ob_error *error = nullptr; + impl_ = ob_create_pipeline(&error); + Error::handle(&error); + } + + /** + * @brief + * Pipeline(std::shared_ptr< Device > device ) Function for multi-device operations. Multiple devices need to be obtained through DeviceList, and the device + * and pipeline are bound through this interface. + */ + explicit Pipeline(std::shared_ptr device) { + ob_error *error = nullptr; + impl_ = ob_create_pipeline_with_device(device->getImpl(), &error); + Error::handle(&error); + } + + /** + * @brief Destroy the pipeline object + */ + ~Pipeline() noexcept { + ob_error *error = nullptr; + ob_delete_pipeline(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Start the pipeline with configuration parameters + * + * @param config The parameter configuration of the pipeline + */ + void start(std::shared_ptr config = nullptr) const { + ob_error *error = nullptr; + ob_config_t *config_impl = config == nullptr ? nullptr : config->getImpl(); + ob_pipeline_start_with_config(impl_, config_impl, &error); + Error::handle(&error); + } + + /** + * @brief Start the pipeline and set the frameset data callback + * + * @param config The configuration of the pipeline + * @param callback The callback to be triggered when all frame data in the frameset arrives + */ + void start(std::shared_ptr config, FrameSetCallback callback) { + callback_ = callback; + ob_error *error = nullptr; + ob_pipeline_start_with_callback(impl_, config ? config->getImpl() : nullptr, &Pipeline::frameSetCallback, this, &error); + Error::handle(&error); + } + + static void frameSetCallback(ob_frame_t *frameSet, void *userData) { + auto pipeline = static_cast(userData); + pipeline->callback_(std::make_shared(frameSet)); + } + + /** + * @brief Stop the pipeline + */ + void stop() const { + ob_error *error = nullptr; + ob_pipeline_stop(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Get the pipeline configuration parameters + * @brief Returns the default configuration if the user has not configured it + * + * @return std::shared_ptr The configured parameters + */ + std::shared_ptr getConfig() const { + ob_error *error = nullptr; + auto config = ob_pipeline_get_config(impl_, &error); + Error::handle(&error); + return std::make_shared(config); + } + + /** + * @brief Wait for frameset + * + * @param timeoutMs The waiting timeout in milliseconds + * @return std::shared_ptr The waiting frameset data + */ + std::shared_ptr waitForFrameset(uint32_t timeoutMs = 1000) const { + ob_error *error = nullptr; + auto frameSet = ob_pipeline_wait_for_frameset(impl_, timeoutMs, &error); + if(frameSet == nullptr) { + return nullptr; + } + Error::handle(&error); + return std::make_shared(frameSet); + } + + /** + * @brief Get the device object + * + * @return std::shared_ptr The device object + */ + std::shared_ptr getDevice() const { + ob_error *error = nullptr; + auto device = ob_pipeline_get_device(impl_, &error); + Error::handle(&error); + return std::make_shared(device); + } + + /** + * @brief Get the stream profile of the specified sensor + * + * @param sensorType The type of sensor + * @return std::shared_ptr The stream profile list + */ + std::shared_ptr getStreamProfileList(OBSensorType sensorType) const { + ob_error *error = nullptr; + auto list = ob_pipeline_get_stream_profile_list(impl_, sensorType, &error); + Error::handle(&error); + return std::make_shared(list); + } + + /** + * @brief Get the stream profile list of supported depth-to-color alignments + * + * @param colorProfile The color stream profile, which is the target stream profile for the depth-to-color alignment. + * @param alignMode The alignment mode. + * + * @attention Currently, only ALIGN_D2C_HW_MODE supported. For other align modes, please using the AlignFilter interface. + * + * @return std::shared_ptr The stream profile list of supported depth-to-color alignments. + */ + std::shared_ptr getD2CDepthProfileList(std::shared_ptr colorProfile, OBAlignMode alignMode) { + ob_error *error = nullptr; + auto list = ob_get_d2c_depth_profile_list(impl_, colorProfile->getImpl(), alignMode, &error); + Error::handle(&error); + return std::make_shared(list); + } + + /** + * @brief Turn on frame synchronization + */ + void enableFrameSync() const { + ob_error *error = nullptr; + ob_pipeline_enable_frame_sync(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Turn off frame synchronization + */ + void disableFrameSync() const { + ob_error *error = nullptr; + ob_pipeline_disable_frame_sync(impl_, &error); + Error::handle(&error); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + + OBCameraParam getCameraParam() { + ob_error *error = nullptr; + OBCameraParam cameraParam = ob_pipeline_get_camera_param(impl_, &error); + Error::handle(&error); + return cameraParam; + } + + OBCameraParam getCameraParamWithProfile(uint32_t colorWidth, uint32_t colorHeight, uint32_t depthWidth, uint32_t depthHeight) { + ob_error *error = nullptr; + OBCameraParam cameraParam = ob_pipeline_get_camera_param_with_profile(impl_, colorWidth, colorHeight, depthWidth, depthHeight, &error); + Error::handle(&error); + return cameraParam; + } + + OBCalibrationParam getCalibrationParam(std::shared_ptr config) { + ob_error *error = nullptr; + OBCalibrationParam calibrationParam = ob_pipeline_get_calibration_param(impl_, config->getImpl(), &error); + Error::handle(&error); + return calibrationParam; + } + + std::shared_ptr waitForFrames(uint32_t timeoutMs = 1000) const { + return waitForFrameset(timeoutMs); + } +}; + +} // namespace ob diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/RecordPlayback.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/RecordPlayback.hpp new file mode 100644 index 0000000..fe61de2 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/RecordPlayback.hpp @@ -0,0 +1,188 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file RecordPlayback.hpp + * @brief Record and playback device-related types, including interfaces to create recording and playback devices, + record and playback streaming data, etc. + */ + +#pragma once + +#include "Types.hpp" +#include "Error.hpp" +#include "libobsensor/h/RecordPlayback.h" +#include "libobsensor/hpp/Device.hpp" + +namespace ob { + +typedef std::function PlaybackStatusChangeCallback; + +class RecordDevice { +private: + ob_record_device_t *impl_ = nullptr; + +public: + explicit RecordDevice(std::shared_ptr device, const std::string &file, bool compressionEnabled = true) { + ob_error *error = nullptr; + impl_ = ob_create_record_device(device->getImpl(), file.c_str(), compressionEnabled, &error); + Error::handle(&error); + } + + virtual ~RecordDevice() noexcept { + ob_error *error = nullptr; + ob_delete_record_device(impl_, &error); + Error::handle(&error, false); + } + + RecordDevice(RecordDevice &&other) noexcept { + if(this != &other) { + impl_ = other.impl_; + other.impl_ = nullptr; + } + } + + RecordDevice &operator=(RecordDevice &&other) noexcept { + if(this != &other) { + impl_ = other.impl_; + other.impl_ = nullptr; + } + + return *this; + } + + RecordDevice(const RecordDevice &) = delete; + RecordDevice &operator=(const RecordDevice &) = delete; + +public: + void pause() { + ob_error *error = nullptr; + ob_record_device_pause(impl_, &error); + Error::handle(&error); + } + + void resume() { + ob_error *error = nullptr; + ob_record_device_resume(impl_, &error); + Error::handle(&error); + } +}; + +class PlaybackDevice : public Device { +public: + explicit PlaybackDevice(const std::string &file) : Device(nullptr) { + ob_error *error = nullptr; + impl_ = ob_create_playback_device(file.c_str(), &error); + Error::handle(&error); + } + + virtual ~PlaybackDevice() noexcept override = default; + + PlaybackDevice(PlaybackDevice &&other) noexcept : Device(std::move(other)) {} + + PlaybackDevice &operator=(PlaybackDevice &&other) noexcept { + Device::operator=(std::move(other)); + return *this; + } + + PlaybackDevice(const PlaybackDevice &) = delete; + PlaybackDevice &operator=(const PlaybackDevice &) = delete; + +public: + /** + * @brief Pause the streaming data from the playback device. + */ + void pause() { + ob_error *error = nullptr; + ob_playback_device_pause(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Resume the streaming data from the playback device. + */ + void resume() { + ob_error *error = nullptr; + ob_playback_device_resume(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Seek to a specific timestamp when playing back a recording. + * @param[in] timestamp The timestamp to seek to, in milliseconds. + */ + void seek(const int64_t timestamp) { + ob_error *error = nullptr; + ob_playback_device_seek(impl_, timestamp, &error); + Error::handle(&error); + } + + /** + * @brief Set the playback rate of the playback device. + * @param[in] rate The playback rate to set. + */ + void setPlaybackRate(const float rate) { + ob_error *error = nullptr; + ob_playback_device_set_playback_rate(impl_, rate, &error); + Error::handle(&error); + } + + /** + * @brief Set a callback function to be called when the playback status changes. + * @param[in] callback The callback function to set. + */ + void setPlaybackStatusChangeCallback(PlaybackStatusChangeCallback callback) { + callback_ = callback; + ob_error *error = nullptr; + ob_playback_device_set_playback_status_changed_callback(impl_, &PlaybackDevice::playbackStatusCallback, this, &error); + Error::handle(&error); + } + + /** + * @brief Get the current playback status of the playback device. + * @return The current playback status. + */ + OBPlaybackStatus getPlaybackStatus() const { + ob_error *error = nullptr; + OBPlaybackStatus status = ob_playback_device_get_current_playback_status(impl_, &error); + Error::handle(&error); + + return status; + } + + /** + * @brief Get the current position of the playback device. + * @return The current position of the playback device, in milliseconds. + */ + uint64_t getPosition() const { + ob_error *error = nullptr; + uint64_t position = ob_playback_device_get_position(impl_, &error); + Error::handle(&error); + + return position; + } + + /** + * @brief Get the duration of the playback device. + * @return The duration of the playback device, in milliseconds. + */ + uint64_t getDuration() const { + ob_error *error = nullptr; + uint64_t duration = ob_playback_device_get_duration(impl_, &error); + Error::handle(&error); + + return duration; + } + +private: + static void playbackStatusCallback(OBPlaybackStatus status, void *userData) { + auto *playbackDevice = static_cast(userData); + if(playbackDevice && playbackDevice->callback_) { + playbackDevice->callback_(status); + } + } + +private: + PlaybackStatusChangeCallback callback_; +}; +} // namespace ob \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Sensor.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Sensor.hpp new file mode 100644 index 0000000..73345f2 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Sensor.hpp @@ -0,0 +1,236 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Sensor.hpp + * @brief Defines types related to sensors, which are used to obtain stream configurations, open and close streams, and set and get sensor properties. + */ +#pragma once + +#include "Types.hpp" +#include "libobsensor/hpp/Filter.hpp" +#include "libobsensor/h/Sensor.h" +#include "libobsensor/h/Filter.h" +#include "Error.hpp" +#include "StreamProfile.hpp" +#include "Device.hpp" +#include "Frame.hpp" +#include +#include +#include + +namespace ob { + +class Sensor { +public: + /** + * @brief Callback function for frame data. + * + * @param frame The frame data. + */ + typedef std::function frame)> FrameCallback; + +protected: + ob_sensor_t *impl_; + FrameCallback callback_; + +public: + explicit Sensor(ob_sensor_t *impl) : impl_(impl) {} + + Sensor(Sensor &&sensor) noexcept : impl_(sensor.impl_) { + sensor.impl_ = nullptr; + } + + Sensor &operator=(Sensor &&sensor) noexcept { + if(this != &sensor) { + ob_error *error = nullptr; + ob_delete_sensor(impl_, &error); + Error::handle(&error); + impl_ = sensor.impl_; + sensor.impl_ = nullptr; + } + return *this; + } + + Sensor(const Sensor &sensor) = delete; + Sensor &operator=(const Sensor &sensor) = delete; + + virtual ~Sensor() noexcept { + ob_error *error = nullptr; + ob_delete_sensor(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get the sensor type. + * + * @return OBSensorType The sensor type. + */ + OBSensorType getType() const { + ob_error *error = nullptr; + auto type = ob_sensor_get_type(impl_, &error); + Error::handle(&error); + return type; + } + + /** + * @brief Get the list of stream profiles. + * + * @return std::shared_ptr The stream profile list. + */ + std::shared_ptr getStreamProfileList() const { + ob_error *error = nullptr; + auto list = ob_sensor_get_stream_profile_list(impl_, &error); + Error::handle(&error); + return std::make_shared(list); + } + + /** + * @brief Create a list of recommended filters for the sensor. + * + * @return OBFilterList list of frame processing block + */ + std::vector> createRecommendedFilters() const { + ob_error *error = nullptr; + auto list = ob_sensor_create_recommended_filter_list(impl_, &error); + Error::handle(&error); + auto filter_count = ob_filter_list_get_count(list, &error); + + std::vector> filters; + for(uint32_t i = 0; i < filter_count; i++) { + auto filterImpl = ob_filter_list_get_filter(list, i, &error); + Error::handle(&error); + filters.push_back(std::make_shared(filterImpl)); + } + ob_delete_filter_list(list, &error); + Error::handle(&error, false); + return filters; + } + + /** + * @brief Open a frame data stream and set up a callback. + * + * @param streamProfile The stream configuration. + * @param callback The callback to set when frame data arrives. + */ + void start(std::shared_ptr streamProfile, FrameCallback callback) { + ob_error *error = nullptr; + callback_ = std::move(callback); + ob_sensor_start(impl_, const_cast(streamProfile->getImpl()), &Sensor::frameCallback, this, &error); + Error::handle(&error); + } + + /** + * @brief Stop the stream. + */ + void stop() const { + ob_error *error = nullptr; + ob_sensor_stop(impl_, &error); + Error::handle(&error); + } + + /** + * @brief Dynamically switch resolutions. + * + * @param streamProfile The resolution to switch to. + */ + void switchProfile(std::shared_ptr streamProfile) { + ob_error *error = nullptr; + ob_sensor_switch_profile(impl_, const_cast(streamProfile->getImpl()), &error); + Error::handle(&error); + } + +private: + static void frameCallback(ob_frame *frame, void *userData) { + auto sensor = static_cast(userData); + sensor->callback_(std::make_shared(frame)); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + OBSensorType type() const { + return getType(); + } + + std::vector> getRecommendedFilters() const { + return createRecommendedFilters(); + } +}; + +class SensorList { +private: + ob_sensor_list_t *impl_ = nullptr; + +public: + explicit SensorList(ob_sensor_list_t *impl) : impl_(impl) {} + + ~SensorList() noexcept { + ob_error *error = nullptr; + ob_delete_sensor_list(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Get the number of sensors. + * + * @return uint32_t The number of sensors. + */ + uint32_t getCount() const { + ob_error *error = nullptr; + auto count = ob_sensor_list_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Get the type of the specified sensor. + * + * @param index The sensor index. + * @return OBSensorType The sensor type. + */ + OBSensorType getSensorType(uint32_t index) const { + ob_error *error = nullptr; + auto type = ob_sensor_list_get_sensor_type(impl_, index, &error); + Error::handle(&error); + return type; + } + + /** + * @brief Get a sensor by index number. + * + * @param index The sensor index. The range is [0, count-1]. If the index exceeds the range, an exception will be thrown. + * @return std::shared_ptr The sensor object. + */ + std::shared_ptr getSensor(uint32_t index) const { + ob_error *error = nullptr; + auto sensor = ob_sensor_list_get_sensor(impl_, index, &error); + Error::handle(&error); + return std::make_shared(sensor); + } + + /** + * @brief Get a sensor by sensor type. + * + * @param sensorType The sensor type to obtain. + * @return std::shared_ptr A sensor object. If the specified sensor type does not exist, it will return empty. + */ + std::shared_ptr getSensor(OBSensorType sensorType) const { + ob_error *error = nullptr; + auto sensor = ob_sensor_list_get_sensor_by_type(impl_, sensorType, &error); + Error::handle(&error); + return std::make_shared(sensor); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t count() const { + return getCount(); + } + + OBSensorType type(uint32_t index) const { + return getSensorType(index); + } +}; + +} // namespace ob + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/StreamProfile.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/StreamProfile.hpp new file mode 100644 index 0000000..01c068d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/StreamProfile.hpp @@ -0,0 +1,523 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file StreamProfile.hpp + * @brief The stream profile related type is used to get information such as the width, height, frame rate, and format of the stream. + */ +#pragma once + +#include "Types.hpp" +#include "libobsensor/h/StreamProfile.h" +#include "libobsensor/h/Error.h" +#include +#include + +namespace ob { + +class StreamProfile : public std::enable_shared_from_this { +protected: + const ob_stream_profile_t *impl_ = nullptr; + +public: + StreamProfile(StreamProfile &streamProfile) = delete; + StreamProfile &operator=(StreamProfile &streamProfile) = delete; + + StreamProfile(StreamProfile &&streamProfile) noexcept : impl_(streamProfile.impl_) { + streamProfile.impl_ = nullptr; + } + + StreamProfile &operator=(StreamProfile &&streamProfile) noexcept { + if(this != &streamProfile) { + ob_error *error = nullptr; + ob_delete_stream_profile(impl_, &error); + Error::handle(&error); + impl_ = streamProfile.impl_; + streamProfile.impl_ = nullptr; + } + return *this; + } + + virtual ~StreamProfile() noexcept { + if(impl_) { + ob_error *error = nullptr; + ob_delete_stream_profile(impl_, &error); + Error::handle(&error); + } + } + + const ob_stream_profile *getImpl() const { + return impl_; + } + + /** + * @brief Get the format of the stream. + * + * @return OBFormat return the format of the stream. + */ + OBFormat getFormat() const { + ob_error *error = nullptr; + auto format = ob_stream_profile_get_format(impl_, &error); + Error::handle(&error); + return format; + } + + /** + * @brief Get the type of stream. + * + * @return OBStreamType return the type of the stream. + */ + OBStreamType getType() const { + ob_error *error = nullptr; + auto type = ob_stream_profile_get_type(impl_, &error); + Error::handle(&error); + return type; + } + + /** + * @brief Get the extrinsic parameters from current stream profile to the given target stream profile. + * + * @return OBExtrinsic Return the extrinsic parameters. + */ + OBExtrinsic getExtrinsicTo(std::shared_ptr target) const { + ob_error *error = nullptr; + auto extrinsic = ob_stream_profile_get_extrinsic_to(impl_, const_cast(target->getImpl()), &error); + Error::handle(&error); + return extrinsic; + } + + /** + * @brief Set the extrinsic parameters from current stream profile to the given target stream profile. + * + * @tparam target Target stream profile. + * @tparam extrinsic The extrinsic. + */ + void bindExtrinsicTo(std::shared_ptr target, const OBExtrinsic &extrinsic) { + ob_error *error = nullptr; + ob_stream_profile_set_extrinsic_to(const_cast(impl_), const_cast(target->getImpl()), extrinsic, &error); + Error::handle(&error); + } + + /** + * @brief Set the extrinsic parameters from current stream profile to the given target stream type. + * + * @tparam targetStreamType Target stream type. + * @tparam extrinsic The extrinsic. + */ + void bindExtrinsicTo(const OBStreamType &targetStreamType, const OBExtrinsic &extrinsic) { + ob_error *error = nullptr; + ob_stream_profile_set_extrinsic_to_type(const_cast(impl_),targetStreamType,extrinsic, &error); + Error::handle(&error); + } + + /** + * @brief Check if frame object is compatible with the given type. + * + * @tparam T Given type. + * @return bool return result. + */ + template bool is() const; + + /** + * @brief Converts object type to target type. + * + * @tparam T Target type. + * @return std::shared_ptr Return the result. Throws an exception if conversion is not possible. + */ + template std::shared_ptr as() { + if(!is()) { + throw std::runtime_error("Unsupported operation. Object's type is not the required type."); + } + + return std::dynamic_pointer_cast(shared_from_this()); + } + + /** + * @brief Converts object type to target type (const version). + * + * @tparam T Target type. + * @return std::shared_ptr Return the result. Throws an exception if conversion is not possible. + */ + template std::shared_ptr as() const { + if(!is()) { + throw std::runtime_error("Unsupported operation. Object's type is not the required type."); + } + + return std::static_pointer_cast(shared_from_this()); + } + + // The following interfaces are deprecated and are retained here for compatibility purposes. + OBFormat format() const { + return getFormat(); + } + + OBStreamType type() const { + return getType(); + } + +protected: + explicit StreamProfile(const ob_stream_profile_t *impl) : impl_(impl) {} +}; + +/** + * @brief Class representing a video stream profile. + */ +class VideoStreamProfile : public StreamProfile { +public: + explicit VideoStreamProfile(const ob_stream_profile_t *impl) : StreamProfile(impl) {} + + ~VideoStreamProfile() noexcept override = default; + + /** + * @brief Return the frame rate of the stream. + * + * @return uint32_t Return the frame rate of the stream. + */ + uint32_t getFps() const { + ob_error *error = nullptr; + auto fps = ob_video_stream_profile_get_fps(impl_, &error); + Error::handle(&error); + return fps; + } + + /** + * @brief Return the width of the stream. + * + * @return uint32_t Return the width of the stream. + */ + uint32_t getWidth() const { + ob_error *error = nullptr; + auto width = ob_video_stream_profile_get_width(impl_, &error); + Error::handle(&error); + return width; + } + + /** + * @brief Return the height of the stream. + * + * @return uint32_t Return the height of the stream. + */ + uint32_t getHeight() const { + ob_error *error = nullptr; + auto height = ob_video_stream_profile_get_height(impl_, &error); + Error::handle(&error); + return height; + } + + /** + * @brief Get the intrinsic parameters of the stream. + * + * @return OBCameraIntrinsic Return the intrinsic parameters. + */ + OBCameraIntrinsic getIntrinsic() const { + ob_error *error = nullptr; + auto intrinsic = ob_video_stream_profile_get_intrinsic(impl_, &error); + Error::handle(&error); + return intrinsic; + } + + /** + * @brief Set the intrinsic parameters of the stream. + * + * @param intrinsic The intrinsic parameters. + */ + void setIntrinsic(const OBCameraIntrinsic &intrinsic) { + ob_error *error = nullptr; + ob_video_stream_profile_set_intrinsic(const_cast(impl_), intrinsic, &error); + Error::handle(&error); + } + + /** + * @brief Get the distortion parameters of the stream. + * @brief Brown distortion model + * + * @return OBCameraDistortion Return the distortion parameters. + */ + OBCameraDistortion getDistortion() const { + ob_error *error = nullptr; + auto distortion = ob_video_stream_profile_get_distortion(impl_, &error); + Error::handle(&error); + return distortion; + } + + /** + * @brief Set the distortion parameters of the stream. + * + * @param distortion The distortion parameters. + */ + void setDistortion(const OBCameraDistortion &distortion) { + ob_error *error = nullptr; + ob_video_stream_profile_set_distortion(const_cast(impl_), distortion, &error); + Error::handle(&error); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t fps() const { + return getFps(); + } + + uint32_t width() const { + return getWidth(); + } + + uint32_t height() const { + return getHeight(); + } +}; + +/** + * @brief Class representing an accelerometer stream profile. + */ +class AccelStreamProfile : public StreamProfile { +public: + explicit AccelStreamProfile(const ob_stream_profile_t *impl) : StreamProfile(impl) {} + + ~AccelStreamProfile() noexcept override = default; + + /** + * @brief Return the full scale range. + * + * @return OBAccelFullScaleRange Return the scale range value. + */ + OBAccelFullScaleRange getFullScaleRange() const { + ob_error *error = nullptr; + auto fullScaleRange = ob_accel_stream_profile_get_full_scale_range(impl_, &error); + Error::handle(&error); + return fullScaleRange; + } + + /** + * @brief Return the sampling frequency. + * + * @return OBAccelFullScaleRange Return the sampling frequency. + */ + OBAccelSampleRate getSampleRate() const { + ob_error *error = nullptr; + auto sampleRate = ob_accel_stream_profile_get_sample_rate(impl_, &error); + Error::handle(&error); + return sampleRate; + } + + /** + * @brief get the intrinsic parameters of the stream. + * + * @return OBAccelIntrinsic Return the intrinsic parameters. + */ + OBAccelIntrinsic getIntrinsic() const { + ob_error *error = nullptr; + auto intrinsic = ob_accel_stream_profile_get_intrinsic(impl_, &error); + Error::handle(&error); + return intrinsic; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + OBAccelFullScaleRange fullScaleRange() const { + return getFullScaleRange(); + } + + OBAccelSampleRate sampleRate() const { + return getSampleRate(); + } +}; + +/** + * @brief Class representing a gyroscope stream profile. + */ +class GyroStreamProfile : public StreamProfile { +public: + explicit GyroStreamProfile(const ob_stream_profile_t *impl) : StreamProfile(impl) {} + + ~GyroStreamProfile() noexcept override = default; + + /** + * @brief Return the full scale range. + * + * @return OBAccelFullScaleRange Return the scale range value. + */ + OBGyroFullScaleRange getFullScaleRange() const { + ob_error *error = nullptr; + auto fullScaleRange = ob_gyro_stream_profile_get_full_scale_range(impl_, &error); + Error::handle(&error); + return fullScaleRange; + } + + /** + * @brief Return the sampling frequency. + * + * @return OBAccelFullScaleRange Return the sampling frequency. + */ + OBGyroSampleRate getSampleRate() const { + ob_error *error = nullptr; + auto sampleRate = ob_gyro_stream_profile_get_sample_rate(impl_, &error); + Error::handle(&error); + return sampleRate; + } + + /** + * @brief get the intrinsic parameters of the stream. + * + * @return OBGyroIntrinsic Return the intrinsic parameters. + */ + OBGyroIntrinsic getIntrinsic() const { + ob_error *error = nullptr; + auto intrinsic = ob_gyro_stream_get_intrinsic(impl_, &error); + Error::handle(&error); + return intrinsic; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + OBGyroFullScaleRange fullScaleRange() const { + return getFullScaleRange(); + } + + OBGyroSampleRate sampleRate() const { + return getSampleRate(); + } +}; + +template bool StreamProfile::is() const { + switch(this->getType()) { + case OB_STREAM_VIDEO: + case OB_STREAM_IR: + case OB_STREAM_IR_LEFT: + case OB_STREAM_IR_RIGHT: + case OB_STREAM_COLOR: + case OB_STREAM_DEPTH: + case OB_STREAM_RAW_PHASE: + case OB_STREAM_CONFIDENCE: + return typeid(T) == typeid(VideoStreamProfile); + case OB_STREAM_ACCEL: + return typeid(T) == typeid(AccelStreamProfile); + case OB_STREAM_GYRO: + return typeid(T) == typeid(GyroStreamProfile); + default: + break; + } + return false; +} + +class StreamProfileFactory { +public: + static std::shared_ptr create(const ob_stream_profile_t *impl) { + ob_error *error = nullptr; + const auto type = ob_stream_profile_get_type(impl, &error); + Error::handle(&error); + switch(type) { + case OB_STREAM_IR: + case OB_STREAM_IR_LEFT: + case OB_STREAM_IR_RIGHT: + case OB_STREAM_DEPTH: + case OB_STREAM_COLOR: + case OB_STREAM_VIDEO: + case OB_STREAM_CONFIDENCE: + return std::make_shared(impl); + case OB_STREAM_ACCEL: + return std::make_shared(impl); + case OB_STREAM_GYRO: + return std::make_shared(impl); + default: { + ob_error *err = ob_create_error(OB_STATUS_ERROR, "Unsupported stream type.", "StreamProfileFactory::create", "", OB_EXCEPTION_TYPE_INVALID_VALUE); + Error::handle(&err); + return nullptr; + } + } + } +}; + +class StreamProfileList { +protected: + const ob_stream_profile_list_t *impl_; + +public: + explicit StreamProfileList(ob_stream_profile_list_t *impl) : impl_(impl) {} + ~StreamProfileList() noexcept { + ob_error *error = nullptr; + ob_delete_stream_profile_list(impl_, &error); + Error::handle(&error, false); + } + + /** + * @brief Return the number of StreamProfile objects. + * + * @return uint32_t Return the number of StreamProfile objects. + */ + uint32_t getCount() const { + ob_error *error = nullptr; + auto count = ob_stream_profile_list_get_count(impl_, &error); + Error::handle(&error); + return count; + } + + /** + * @brief Return the StreamProfile object at the specified index. + * + * @param index The index of the StreamProfile object to be retrieved. Must be in the range [0, count-1]. Throws an exception if the index is out of range. + * @return std::shared_ptr Return the StreamProfile object. + */ + std::shared_ptr getProfile(uint32_t index) const { + ob_error *error = nullptr; + auto profile = ob_stream_profile_list_get_profile(impl_, index, &error); + Error::handle(&error); + return StreamProfileFactory::create(profile); + } + + /** + * @brief Match the corresponding video stream profile based on the passed-in parameters. If multiple Match are found, the first one in the list is + * returned by default. Throws an exception if no matching profile is found. + * + * @param width The width of the stream. Pass OB_WIDTH_ANY if no matching condition is required. + * @param height The height of the stream. Pass OB_HEIGHT_ANY if no matching condition is required. + * @param format The type of the stream. Pass OB_FORMAT_ANY if no matching condition is required. + * @param fps The frame rate of the stream. Pass OB_FPS_ANY if no matching condition is required. + * @return std::shared_ptr Return the matching resolution. + */ + std::shared_ptr getVideoStreamProfile(int width = OB_WIDTH_ANY, int height = OB_HEIGHT_ANY, OBFormat format = OB_FORMAT_ANY, + int fps = OB_FPS_ANY) const { + ob_error *error = nullptr; + auto profile = ob_stream_profile_list_get_video_stream_profile(impl_, width, height, format, fps, &error); + Error::handle(&error); + auto vsp = StreamProfileFactory::create(profile); + return vsp->as(); + } + + /** + * @brief Match the corresponding accelerometer stream profile based on the passed-in parameters. If multiple Match are found, the first one in the list + * is returned by default. Throws an exception if no matching profile is found. + * + * @param fullScaleRange The full scale range. Pass 0 if no matching condition is required. + * @param sampleRate The sampling frequency. Pass 0 if no matching condition is required. + */ + std::shared_ptr getAccelStreamProfile(OBAccelFullScaleRange fullScaleRange, OBAccelSampleRate sampleRate) const { + ob_error *error = nullptr; + auto profile = ob_stream_profile_list_get_accel_stream_profile(impl_, fullScaleRange, sampleRate, &error); + Error::handle(&error); + auto asp = StreamProfileFactory::create(profile); + return asp->as(); + } + + /** + * @brief Match the corresponding gyroscope stream profile based on the passed-in parameters. If multiple Match are found, the first one in the list is + * returned by default. Throws an exception if no matching profile is found. + * + * @param fullScaleRange The full scale range. Pass 0 if no matching condition is required. + * @param sampleRate The sampling frequency. Pass 0 if no matching condition is required. + */ + std::shared_ptr getGyroStreamProfile(OBGyroFullScaleRange fullScaleRange, OBGyroSampleRate sampleRate) const { + ob_error *error = nullptr; + auto profile = ob_stream_profile_list_get_gyro_stream_profile(impl_, fullScaleRange, sampleRate, &error); + Error::handle(&error); + auto gsp = StreamProfileFactory::create(profile); + return gsp->as(); + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + uint32_t count() const { + return getCount(); + } +}; + +} // namespace ob diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/TypeHelper.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/TypeHelper.hpp new file mode 100644 index 0000000..9a4bbe0 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/TypeHelper.hpp @@ -0,0 +1,145 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include "libobsensor/h/ObTypes.h" +#include "libobsensor/h/TypeHelper.h" + +#include + +namespace ob { +class TypeHelper { +public: + /** + * @brief Convert OBFormat to " string " type and then return. + * + * @param[in] type OBFormat type. + * @return OBFormat of "string" type. + */ + static std::string convertOBFormatTypeToString(const OBFormat &type) { + return ob_format_type_to_string(type); + } + + /** + * @brief Convert OBFrameType to " string " type and then return. + * + * @param[in] type OBFrameType type. + * @return OBFrameType of "string" type. + */ + static std::string convertOBFrameTypeToString(const OBFrameType &type) { + return ob_frame_type_to_string(type); + } + + /** + * @brief Convert OBStreamType to " string " type and then return. + * + * @param[in] type OBStreamType type. + * @return OBStreamType of "string" type. + */ + static std::string convertOBStreamTypeToString(const OBStreamType &type) { + return ob_stream_type_to_string(type); + } + + /** + * @brief Convert OBSensorType to " string " type and then return. + * + * @param[in] type OBSensorType type. + * @return OBSensorType of "string" type. + */ + static std::string convertOBSensorTypeToString(const OBSensorType &type) { + return ob_sensor_type_to_string(type); + } + + /** + * @brief Convert OBIMUSampleRate to " string " type and then return. + * + * @param[in] type OBIMUSampleRate type. + * @return OBIMUSampleRate of "string" type. + */ + static std::string convertOBIMUSampleRateTypeToString(const OBIMUSampleRate &type) { + return ob_imu_rate_type_to_string(type); + } + + /** + * @brief Convert OBGyroFullScaleRange to " string " type and then return. + * + * @param[in] type OBGyroFullScaleRange type. + * @return OBGyroFullScaleRange of "string" type. + */ + static std::string convertOBGyroFullScaleRangeTypeToString(const OBGyroFullScaleRange &type) { + return ob_gyro_range_type_to_string(type); + } + + /** + * @brief Convert OBAccelFullScaleRange to " string " type and then return. + * + * @param[in] type OBAccelFullScaleRange type. + * @return OBAccelFullScaleRange of "string" type. + */ + static std::string convertOBAccelFullScaleRangeTypeToString(const OBAccelFullScaleRange &type) { + return ob_accel_range_type_to_string(type); + } + + /** + * @brief Convert OBFrameMetadataType to " string " type and then return. + * + * @param[in] type OBFrameMetadataType type. + * @return OBFrameMetadataType of "string" type. + */ + static std::string convertOBFrameMetadataTypeToString(const OBFrameMetadataType &type) { + return ob_meta_data_type_to_string(type); + } + + /** + * @brief Convert OBSensorType to OBStreamType type and then return. + * + * @param[in] type OBSensorType type. + * @return OBStreamType type. + */ + static OBStreamType convertSensorTypeToStreamType(OBSensorType type) { + return ob_sensor_type_to_stream_type(type); + } + + /** + * @brief Check if the given sensor type is a video sensor. + * @brief Video sensors are sensors that produce video frames. + * @brief The following sensor types are considered video sensors: + * OB_SENSOR_COLOR, + * OB_SENSOR_DEPTH, + * OB_SENSOR_IR, + * OB_SENSOR_IR_LEFT, + * OB_SENSOR_IR_RIGHT, + * OB_SENSOR_CONFIDENCE, + * + * @param type The sensor type + * @return true + * @return false + */ + static bool isVideoSensorType(OBSensorType type) { + return ob_is_video_sensor_type(type); + } + + /** + * @brief Check if the given stream type is a video stream. + * @brief Video streams are streams that contain video frames. + * @brief The following stream types are considered video streams: + * OB_STREAM_VIDEO, + * OB_STREAM_DEPTH, + * OB_STREAM_COLOR, + * OB_STREAM_IR, + * OB_STREAM_IR_LEFT, + * OB_STREAM_IR_RIGHT, + * OB_STREAM_CONFIDENCE, + * + * @param type The stream type to check. + * @return true if the given stream type is a video stream, false otherwise. + */ + static bool isVideoStreamType(OBStreamType type) { + return ob_is_video_stream_type(type); + } +}; +} // namespace ob + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Types.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Types.hpp new file mode 100644 index 0000000..420f1a0 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Types.hpp @@ -0,0 +1,5 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +#pragma once +#include diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Utils.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Utils.hpp new file mode 100644 index 0000000..7a2a8cf --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Utils.hpp @@ -0,0 +1,190 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Utils.hpp + * @brief The SDK utils class + * + */ +#pragma once +#include "libobsensor/h/Utils.h" +#include "Device.hpp" +#include "Types.hpp" +#include "Frame.hpp" + +#include + +namespace ob { +class Device; + +class CoordinateTransformHelper { +public: + /** + * @brief Transform a 3d point of a source coordinate system into a 3d point of the target coordinate system. + * + * @param[in] source_point3f Source 3d point value + * @param[in] extrinsic Transformation matrix from source to target + * @param[out] target_point3f Target 3d point value + * + * @return bool Transform result + */ + static bool transformation3dto3d(const OBPoint3f source_point3f, OBExtrinsic extrinsic, OBPoint3f *target_point3f) { + ob_error *error = NULL; + bool result = ob_transformation_3d_to_3d(source_point3f, extrinsic, target_point3f, &error); + Error::handle(&error); + return result; + } + + /** + * @brief Transform a 2d pixel coordinate with an associated depth value of the source camera into a 3d point of the target coordinate system. + * + * @param[in] source_intrinsic Source intrinsic parameters + * @param[in] source_point2f Source 2d point value + * @param[in] source_depth_pixel_value The depth of sourcePoint2f in millimeters + * @param[in] extrinsic Transformation matrix from source to target + * @param[out] target_point3f Target 3d point value + * + * @return bool Transform result + */ + static bool transformation2dto3d(const OBPoint2f source_point2f, const float source_depth_pixel_value, const OBCameraIntrinsic source_intrinsic, + OBExtrinsic extrinsic, OBPoint3f *target_point3f) { + ob_error *error = NULL; + bool result = ob_transformation_2d_to_3d(source_point2f, source_depth_pixel_value, source_intrinsic, extrinsic, target_point3f, &error); + Error::handle(&error); + return result; + } + + /** + * @brief Transform a 3d point of a source coordinate system into a 2d pixel coordinate of the target camera. + * + * @param[in] source_point3f Source 3d point value + * @param[in] target_intrinsic Target intrinsic parameters + * @param[in] target_distortion Target distortion parameters + * @param[in] extrinsic Transformation matrix from source to target + * @param[out] target_point2f Target 2d point value + * + * @return bool Transform result + */ + static bool transformation3dto2d(const OBPoint3f source_point3f, const OBCameraIntrinsic target_intrinsic, const OBCameraDistortion target_distortion, + OBExtrinsic extrinsic, OBPoint2f *target_point2f) { + ob_error *error = NULL; + bool result = ob_transformation_3d_to_2d(source_point3f, target_intrinsic, target_distortion, extrinsic, target_point2f, &error); + Error::handle(&error); + return result; + } + + /** + * @brief Transform a 2d pixel coordinate with an associated depth value of the source camera into a 2d pixel coordinate of the target camera + * + * @param[in] source_intrinsic Source intrinsic parameters + * @param[in] source_distortion Source distortion parameters + * @param[in] source_point2f Source 2d point value + * @param[in] source_depth_pixel_value The depth of sourcePoint2f in millimeters + * @param[in] target_intrinsic Target intrinsic parameters + * @param[in] target_distortion Target distortion parameters + * @param[in] extrinsic Transformation matrix from source to target + * @param[out] target_point2f Target 2d point value + * + * @return bool Transform result + */ + static bool transformation2dto2d(const OBPoint2f source_point2f, const float source_depth_pixel_value, const OBCameraIntrinsic source_intrinsic, + const OBCameraDistortion source_distortion, const OBCameraIntrinsic target_intrinsic, + const OBCameraDistortion target_distortion, OBExtrinsic extrinsic, OBPoint2f *target_point2f) { + ob_error *error = NULL; + bool result = ob_transformation_2d_to_2d(source_point2f, source_depth_pixel_value, source_intrinsic, source_distortion, target_intrinsic, + target_distortion, extrinsic, target_point2f, &error); + Error::handle(&error); + return result; + } + +public: + // The following interfaces are deprecated and are retained here for compatibility purposes. + static bool calibration3dTo3d(const OBCalibrationParam calibrationParam, const OBPoint3f sourcePoint3f, const OBSensorType sourceSensorType, + const OBSensorType targetSensorType, OBPoint3f *targetPoint3f) { + ob_error *error = NULL; + bool result = ob_calibration_3d_to_3d(calibrationParam, sourcePoint3f, sourceSensorType, targetSensorType, targetPoint3f, &error); + Error::handle(&error); + return result; + } + + static bool calibration2dTo3d(const OBCalibrationParam calibrationParam, const OBPoint2f sourcePoint2f, const float sourceDepthPixelValue, + const OBSensorType sourceSensorType, const OBSensorType targetSensorType, OBPoint3f *targetPoint3f) { + ob_error *error = NULL; + bool result = + ob_calibration_2d_to_3d(calibrationParam, sourcePoint2f, sourceDepthPixelValue, sourceSensorType, targetSensorType, targetPoint3f, &error); + Error::handle(&error); + return result; + } + + static bool calibration3dTo2d(const OBCalibrationParam calibrationParam, const OBPoint3f sourcePoint3f, const OBSensorType sourceSensorType, + const OBSensorType targetSensorType, OBPoint2f *targetPoint2f) { + ob_error *error = NULL; + bool result = ob_calibration_3d_to_2d(calibrationParam, sourcePoint3f, sourceSensorType, targetSensorType, targetPoint2f, &error); + Error::handle(&error); + return result; + } + + static bool calibration2dTo2d(const OBCalibrationParam calibrationParam, const OBPoint2f sourcePoint2f, const float sourceDepthPixelValue, + const OBSensorType sourceSensorType, const OBSensorType targetSensorType, OBPoint2f *targetPoint2f) { + ob_error *error = NULL; + bool result = + ob_calibration_2d_to_2d(calibrationParam, sourcePoint2f, sourceDepthPixelValue, sourceSensorType, targetSensorType, targetPoint2f, &error); + Error::handle(&error); + return result; + } + + static std::shared_ptr transformationDepthFrameToColorCamera(std::shared_ptr device, std::shared_ptr depthFrame, + uint32_t targetColorCameraWidth, uint32_t targetColorCameraHeight) { + ob_error *error = NULL; + + // unsafe operation, need to cast const to non-const + auto unConstImpl = const_cast(depthFrame->getImpl()); + + auto result = transformation_depth_frame_to_color_camera(device->getImpl(), unConstImpl, targetColorCameraWidth, targetColorCameraHeight, &error); + Error::handle(&error); + return std::make_shared(result); + } + + static bool transformationInitXYTables(const OBCalibrationParam calibrationParam, const OBSensorType sensorType, float *data, uint32_t *dataSize, + OBXYTables *xyTables) { + ob_error *error = NULL; + bool result = transformation_init_xy_tables(calibrationParam, sensorType, data, dataSize, xyTables, &error); + Error::handle(&error); + return result; + } + + static void transformationDepthToPointCloud(OBXYTables *xyTables, const void *depthImageData, void *pointCloudData) { + ob_error *error = NULL; + transformation_depth_to_pointcloud(xyTables, depthImageData, pointCloudData, &error); + Error::handle(&error, false); + } + + static void transformationDepthToRGBDPointCloud(OBXYTables *xyTables, const void *depthImageData, const void *colorImageData, void *pointCloudData) { + ob_error *error = NULL; + transformation_depth_to_rgbd_pointcloud(xyTables, depthImageData, colorImageData, pointCloudData, &error); + Error::handle(&error, false); + } +}; + +class PointCloudHelper { +public: + /** + * @brief save point cloud to ply file. + * + * @param[in] fileName Point cloud save path + * @param[in] frame Point cloud frame + * @param[in] saveBinary Binary or textual,true: binary, false: textual + * @param[in] useMesh Save mesh or not, true: save as mesh, false: not save as mesh + * @param[in] meshThreshold Distance threshold for creating faces in point cloud,default value :50 + * + * @return bool save point cloud result + */ + static bool savePointcloudToPly(const char *fileName, std::shared_ptr frame, bool saveBinary, bool useMesh, float meshThreshold) { + ob_error *error = NULL; + auto unConstImpl = const_cast(frame->getImpl()); + bool result = ob_save_pointcloud_to_ply(fileName, unConstImpl, saveBinary, useMesh, meshThreshold, &error); + Error::handle(&error, false); + return result; + } +}; +} // namespace ob diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Version.hpp b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Version.hpp new file mode 100644 index 0000000..21b5246 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/include/libobsensor/hpp/Version.hpp @@ -0,0 +1,62 @@ +// Copyright (c) Orbbec Inc. All Rights Reserved. +// Licensed under the MIT License. + +/** + * @file Version.hpp + * @brief Provides functions to retrieve version information of the SDK. + */ +#pragma once + +#include "libobsensor/h/Version.h" + +namespace ob { +class Version { +public: + /** + * @brief Get the full version number of the SDK. + * @brief The full version number equals to: major * 10000 + minor * 100 + patch + * + * @return int The full version number of the SDK. + */ + static int getVersion() { + return ob_get_version(); + } + /** + * @brief Get the major version number of the SDK. + * + * @return int The major version number of the SDK. + */ + static int getMajor() { + return ob_get_major_version(); + } + + /** + * @brief Get the minor version number of the SDK. + * + * @return int The minor version number of the SDK. + */ + static int getMinor() { + return ob_get_minor_version(); + } + + /** + * @brief Get the patch version number of the SDK. + * + * @return int The patch version number of the SDK. + */ + static int getPatch() { + return ob_get_patch_version(); + } + + /** + * @brief Get the stage version of the SDK. + * @brief The stage version string is a vendor defined string for some special releases. + * + * @return char* The stage version string of the SDK. + */ + static const char *getStageVersion() { + return ob_get_stage_version(); + } +}; +} // namespace ob + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig-release.cmake b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig-release.cmake new file mode 100644 index 0000000..79983da --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig-release.cmake @@ -0,0 +1,19 @@ +#---------------------------------------------------------------- +# Generated CMake target import file for configuration "Release". +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Import target "ob::OrbbecSDK" for configuration "Release" +set_property(TARGET ob::OrbbecSDK APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(ob::OrbbecSDK PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libOrbbecSDK.so.2.5.5" + IMPORTED_SONAME_RELEASE "libOrbbecSDK.so.2" + ) + +list(APPEND _cmake_import_check_targets ob::OrbbecSDK ) +list(APPEND _cmake_import_check_files_for_ob::OrbbecSDK "${_IMPORT_PREFIX}/lib/libOrbbecSDK.so.2.5.5" ) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig.cmake b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig.cmake new file mode 100644 index 0000000..6a35f67 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig.cmake @@ -0,0 +1,104 @@ +# Generated by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8) + message(FATAL_ERROR "CMake >= 2.8.0 required") +endif() +if(CMAKE_VERSION VERSION_LESS "2.8.3") + message(FATAL_ERROR "CMake >= 2.8.3 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.8.3...3.28) +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_cmake_targets_defined "") +set(_cmake_targets_not_defined "") +set(_cmake_expected_targets "") +foreach(_cmake_expected_target IN ITEMS ob::OrbbecSDK) + list(APPEND _cmake_expected_targets "${_cmake_expected_target}") + if(TARGET "${_cmake_expected_target}") + list(APPEND _cmake_targets_defined "${_cmake_expected_target}") + else() + list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}") + endif() +endforeach() +unset(_cmake_expected_target) +if(_cmake_targets_defined STREQUAL _cmake_expected_targets) + unset(_cmake_targets_defined) + unset(_cmake_targets_not_defined) + unset(_cmake_expected_targets) + unset(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT _cmake_targets_defined STREQUAL "") + string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}") + string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n") +endif() +unset(_cmake_targets_defined) +unset(_cmake_targets_not_defined) +unset(_cmake_expected_targets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target ob::OrbbecSDK +add_library(ob::OrbbecSDK SHARED IMPORTED) + +set_target_properties(ob::OrbbecSDK PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" +) + +# Load information for each installed configuration. +file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/OrbbecSDKConfig-*.cmake") +foreach(_cmake_config_file IN LISTS _cmake_config_files) + include("${_cmake_config_file}") +endforeach() +unset(_cmake_config_file) +unset(_cmake_config_files) + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Loop over all imported files and verify that they actually exist +foreach(_cmake_target IN LISTS _cmake_import_check_targets) + if(CMAKE_VERSION VERSION_LESS "3.28" + OR NOT DEFINED _cmake_import_check_xcframework_for_${_cmake_target} + OR NOT IS_DIRECTORY "${_cmake_import_check_xcframework_for_${_cmake_target}}") + foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}") + if(NOT EXISTS "${_cmake_file}") + message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file + \"${_cmake_file}\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \"${CMAKE_CURRENT_LIST_FILE}\" +but not all the files it references. +") + endif() + endforeach() + endif() + unset(_cmake_file) + unset("_cmake_import_check_files_for_${_cmake_target}") +endforeach() +unset(_cmake_target) +unset(_cmake_import_check_targets) + +# This file does not depend on other imported targets which have +# been exported from the same project but in a separate export set. + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig.md b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig.md new file mode 100644 index 0000000..4a5cdd7 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig.md @@ -0,0 +1,208 @@ +# Orbbec SDK Configuration File Introduction (OrbbecSDKConfig_v1.0.xml) + +The Orbbec SDK configuration file is an XML file that defines the global configuration of the Orbbec SDK. + +Upon initialization, the SDK Context will load a configuration file and apply as global configurations. If you have your own configuration file, you can specify it by entering the file path when calling the Context constructor. If you don't provide a path (or input an empty string), the SDK will look for a file named `OrbbecSDKConfig.xml` in the working directory. If neither the specified file nor the default file is found, the SDK will revert to using the built-in default configuration file. + +## Log Configuration + +Log configuration mainly sets the Log level, the Log level output to the console, the Log level output to a file, configures the path for saving Log files, sets the size of each Log file, and sets the number of Log files. + +```cpp + + + + + 0 + + 0 + + 1 + + + + 100 + + 3 + + false + +``` + +## Memory Configuration + +```cpp + + + true + + 2048 + + 10 + + 10 + +``` + +**Notes** + +1. The default size of the memory pool is 2GB (2048MB). +```cpp + 2048 +``` +2. The default number of frame buffere queue in the Pipeline is 10. If the host machine processes slowly, the SDK will buffer up to 10 frames internally, which may affect the delay time in retrieving frames by the user. If the delay is significant, you can reduce the number of buffers. +```cpp + 10 +``` + +3. The default number of queues in the internal processing unit (Processing unit) is 10. If the host machine processes slowly, the SDK will buffer up to 10 frames internally, which may affect the delay time in retrieving frames by the user. If the delay is significant, you can reduce the number of queues. +```cpp + 10 +``` + +## Global Timestamp + +Based on the device's timestamp and considering data transmission delays, the timestamp is converted to the system timestamp dimension through linear regression. It can be used to synchronize timestamps of multiple different devices. The implementation plan is as follows: + +1. Global timestamp fitter: Regularly obtain the device timestamp and the current system timestamp, and calculate the fitting equation parameters using a linear regression method. +2. Global timestamp converter: Convert the data frame timestamp unit to the same unit as the device timestamp, then calculate the overflow times according to the device timestamp to convert to a 64-bit timestamp, and then convert to a global timestamp according to the fitting parameters output by the global timestamp fitter. + +```cpp + + true + + 1000 + + 100 + +``` + +1. By default, the device time is obtained every eight seconds to update the global timestamp fitter. +```cpp + 1000 +``` + +2. The default queue size of the global timestamp fitter is 10. +```cpp + 100 +``` + +**Notes** + +1. The global timestamp mainly supports the Gemini 330 series. Gemini 2, Gemini 2L, Femto Mega, and Femto Bolt are also supported but not thoroughly tested. If there are stability issues with these devices, the global timestamp function can be turned off. +```cpp + false +``` + +## Pipeline Configuration + +```cpp + + + + + + true + + + true + + + + +``` + +1. Pipeline primarily sets which video streams to enable. By default, only Depth and Color streams are enabled. You can add to enable IR streams, left IR, and right IR as follows. +```cpp + + + true + + + true + + + true + +``` + +## Device Configuration + +```cpp + + + true + + LibUVC + + + + + 0 + + + + 0 + + 5000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + +``` + +1. Set whether to enumerate network devices. Femto Mega and Gemini 2 XL support network functions. If you need to use the network functions of these two devices, you can set this to true. +```cpp + true +``` + +2. Set whether to use LibUVC or V4L2 to receive data on Linux or ARM. V4L2 is not supported by all devices, and we recommend using LibUVC. The Gemini 330 series devices support V4L2, but kernel patches are needed to obtain Metadata data. +```cpp + LibUVC +``` + +3. Set the resolution, frame rate, and data format. \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig.xml b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig.xml new file mode 100644 index 0000000..557ef96 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfig.xml @@ -0,0 +1,2687 @@ + + + + 1.1 + + + + + 5 + + 3 + + + + 100 + + 3 + + false + + + + + true + + 2048 + + 10 + + 10 + + + + + + + + true + + + true + + false + + 1920 + + 1080 + + 30 + + MJPG + ]]> + + + + + + + + + true + + + Auto + + + + V4L2 + + + false + + 1000 + + 100 + + + + + 640 + + 576 + + 30 + + Y16 + + + + + 1920 + + 1080 + + 30 + + MJPG + + + + + 640 + + 576 + + 30 + + Y16 + + + + + + false + + 1000 + + 100 + + + + + 640 + + 576 + + 30 + + Y16 + + + + + 1920 + + 1080 + + 30 + + MJPG + + + + + 640 + + 576 + + 30 + + Y16 + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + + 640 + + 576 + + 30 + + Y16 + + + + + 1920 + + 1080 + + 30 + + MJPG + + + + + 640 + + 576 + + 30 + + Y16 + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + 1280 + + 800 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + + + + + + + + + + 640 + + 400 + + 30 + RLE + + + + 640 + + 400 + + 30 + Y8 + + + + + + + 640 + + 400 + + 15 + RLE + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y10 + + + + 1280 + + 800 + + 30 + Y10 + + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 30 + RLE + + + + 1280 + + 720 + + 30 + + MJPG + + + + 1280 + + 800 + + 30 + + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + 640 + + 400 + + 30 + RLE + + + + 640 + + 400 + + 30 + Y8 + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y10 + + + + 1280 + + 800 + + 30 + Y10 + + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + + + 800 + + 600 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 800 + + 600 + + 30 + Y8 + + + + + + 1600 + + 1200 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 800 + + 600 + + 30 + Y8 + + + + + + 640 + + 480 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 10 + RVL + + + + 1280 + + 800 + + 10 + MJPG + + + + 1280 + + 800 + + 10 + Y8 + + + + 1280 + + 800 + + 10 + Y8 + + + + + + + + + + + 1280 + + 800 + + 20 + MJPG + + + + 640 + + 400 + + 20 + RVL + + + + 640 + + 400 + + 20 + Y8 + + + + 640 + + 400 + + 20 + Y8 + + + + + + 1280 + + 800 + + 10 + MJPG + + + + 1280 + + 800 + + 10 + Y10 + + + + 1280 + + 800 + + 10 + Y10 + + + + + + 1280 + + 800 + + 10 + MJPG + + + + 1280 + + 800 + + 10 + Y8 + + + + 1280 + + 800 + + 10 + Y8 + + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + 1 + + 3000 + + 3000 + + 848 + + 480 + + 30 + Y16 + + + + 1 + + 3000 + + 3000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + 0 + + 1 + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 640 + + 400 + + 15 + Y16 + + + + 0 + + 2000 + + 2000 + + 640 + + 400 + + 15 + MJPG + + + + 640 + + 400 + + 15 + Y8 + + + + 640 + + 400 + + 15 + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + true + false + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 15 + Y16 + + + + 1280 + + 720 + + 15 + + MJPG + + + + 1280 + + 800 + + 15 + + Y8 + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 15 + Y16 + + + + 1280 + + 720 + + 15 + + MJPG + + + + 1280 + + 800 + + 15 + + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + 0 + + + 1.2 + + + true + + 1000 + + 100 + + + + + 15 + + 5000 + + 3000 + + 1280 + + 800 + + 10 + Y16 + + + + 1280 + + 800 + + 10 + MJPG + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y8 + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y8 + + 15 + + 5000 + + 3000 + + + + + 0 + + 5000 + + 3000 + + + + 0 + + 5000 + + 3000 + + + + + + + 1280 + + 800 + + 10 + MJPG + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y10 + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y10 + + 15 + + 5000 + + 3000 + + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 10 + Y12 + + + + 640 + + 480 + + 10 + MJPG + + + + 640 + + 400 + + 10 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 10 + Y12 + + + + 640 + + 400 + + 10 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 480 + + 30 + Y12 + + + + 640 + + 480 + + 30 + Y10 + + + + 640 + + 480 + + 30 + UYVY + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 480 + + 30 + Y12 + + + + 640 + + 480 + + 30 + Y10 + + + + 640 + + 480 + + 30 + UYVY + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 480 + + 30 + Y12 + + + + 640 + + 480 + + 30 + Y10 + + + + 640 + + 480 + + 30 + UYVY + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + 640 + + 480 + + 15 + MJPG + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + 640 + + 480 + + 15 + MJPG + + + + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfigVersion.cmake b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfigVersion.cmake new file mode 100644 index 0000000..fb19e15 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/lib/OrbbecSDKConfigVersion.cmake @@ -0,0 +1,65 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version, +# but only if the requested major version is the same as the current one. +# The variable CVF_VERSION must be set before calling configure_file(). + + +set(PACKAGE_VERSION "2.5.5") + +if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + + if("2.5.5" MATCHES "^([0-9]+)\\.") + set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}") + if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}") + endif() + else() + set(CVF_VERSION_MAJOR "2.5.5") + endif() + + if(PACKAGE_FIND_VERSION_RANGE) + # both endpoints of the range must have the expected major version + math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1") + if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT))) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX))) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + else() + if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() + endif() +endif() + + +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") + return() +endif() + +# check that the installed version has the same 32/64bit-ness as the one which is currently searching: +if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") + math(EXPR installedBits "8 * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/depthengine/libdepthengine.so b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/depthengine/libdepthengine.so new file mode 100644 index 0000000..61c3e38 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/depthengine/libdepthengine.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/depthengine/libdepthengine.so.2.0 b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/depthengine/libdepthengine.so.2.0 new file mode 100644 index 0000000..61c3e38 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/depthengine/libdepthengine.so.2.0 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/filters/libFilterProcessor.so b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/filters/libFilterProcessor.so new file mode 100644 index 0000000..309f7a9 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/filters/libFilterProcessor.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/filters/libob_priv_filter.so b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/filters/libob_priv_filter.so new file mode 100644 index 0000000..1f63a27 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/filters/libob_priv_filter.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/firmwareupdater/libfirmwareupdater.so b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/firmwareupdater/libfirmwareupdater.so new file mode 100644 index 0000000..4a89a3d Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/firmwareupdater/libfirmwareupdater.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/frameprocessor/libob_frame_processor.so b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/frameprocessor/libob_frame_processor.so new file mode 100644 index 0000000..7d5cf10 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/lib/extensions/frameprocessor/libob_frame_processor.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/libOrbbecSDK.so b/VideoProsessing/OrbbecSDK_v2.5.5/lib/libOrbbecSDK.so new file mode 100644 index 0000000..434cd68 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/lib/libOrbbecSDK.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/libOrbbecSDK.so.2 b/VideoProsessing/OrbbecSDK_v2.5.5/lib/libOrbbecSDK.so.2 new file mode 100644 index 0000000..434cd68 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/lib/libOrbbecSDK.so.2 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/lib/libOrbbecSDK.so.2.5.5 b/VideoProsessing/OrbbecSDK_v2.5.5/lib/libOrbbecSDK.so.2.5.5 new file mode 100644 index 0000000..434cd68 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/lib/libOrbbecSDK.so.2.5.5 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/setup.sh b/VideoProsessing/OrbbecSDK_v2.5.5/setup.sh new file mode 100644 index 0000000..a8b4084 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/setup.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +# restore current directory +current_dir=$(pwd) + +# cd to the directory where this script is located +cd "$(dirname "$0")" +project_dir=$(pwd) + +# set up environment variables +shared_dir=$project_dir/shared +examples_dir=$project_dir/examples + +# build the examples +if [ -d "$examples_dir" ] && [ -f "$project_dir/build_examples.sh" ]; then + $project_dir/build_examples.sh +fi + +# install udev rules +if [ -d "$shared_dir" ] && [ -f "$shared_dir/install_udev_rules.sh" ]; then + $shared_dir/install_udev_rules.sh +fi + +cd $current_dir diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/shared/99-obsensor-libusb.rules b/VideoProsessing/OrbbecSDK_v2.5.5/shared/99-obsensor-libusb.rules new file mode 100644 index 0000000..81c0947 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/shared/99-obsensor-libusb.rules @@ -0,0 +1,121 @@ +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0501", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Bootloader_Device" + +# UVC Modules +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0635", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Femto" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0638", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Femto-w" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0668", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Femto-live" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0636", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Astra+" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0637", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Astra+s" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0536", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Astra+_rgb" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0537", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Astra+s_rgb" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0669", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Femto-mega" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="066b", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Femto_Bolt" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0660", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Astra_2" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0670", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_2" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0671", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_2_XL" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0673", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_2_L" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0675", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_2_VL" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0800", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_335" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0801", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_330" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0802", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_dm330" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0803", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_336" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0804", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_335L" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0805", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_330L" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0806", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_dm330L" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0807", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_336L" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="080b", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_335Lg" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="080d", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_336Lg" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="080e", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_335Le" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0810", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_336Le" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0674", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_2_I" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0701", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Dabai_DCL" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="069d", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Astra_Pro2" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0808", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_215" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0809", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_210" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0a12", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Dabai_A" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0a13", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Dabai_AL" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0812", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_345" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0813", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_345Lg" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0816", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="CAM-5330" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0817", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="CAM-5530" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2bc5", ATTRS{idProduct}=="0818", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="Gemini_338" + +# OpenNI Modules +SUBSYSTEM=="usb", ATTR{idProduct}=="0401", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra" +SUBSYSTEM=="usb", ATTR{idProduct}=="0402", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra_s" +SUBSYSTEM=="usb", ATTR{idProduct}=="0403", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra_pro" +SUBSYSTEM=="usb", ATTR{idProduct}=="0404", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra_mini" +SUBSYSTEM=="usb", ATTR{idProduct}=="0407", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra_mini_s" +SUBSYSTEM=="usb", ATTR{idProduct}=="0601", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra_NH_GLST" +SUBSYSTEM=="usb", ATTR{idProduct}=="060b", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="deeyea" +SUBSYSTEM=="usb", ATTR{idProduct}=="050b", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="deeyea_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="060e", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="petrel" +SUBSYSTEM=="usb", ATTR{idProduct}=="050e", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="petrel_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="060f", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astro_pro_plus" +SUBSYSTEM=="usb", ATTR{idProduct}=="050f", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astro_pro_plus_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0610", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="bus_cl" +SUBSYSTEM=="usb", ATTR{idProduct}=="0603", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="atlas" +SUBSYSTEM=="usb", ATTR{idProduct}=="0510", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="atlas_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0614", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="gemini" +SUBSYSTEM=="usb", ATTR{idProduct}=="0511", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="gemini_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0616", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="u1s" +SUBSYSTEM=="usb", ATTR{idProduct}=="0516", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="u1s_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0617", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="projector" +SUBSYSTEM=="usb", ATTR{idProduct}=="0517", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="projector_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0618", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="butterfly" +SUBSYSTEM=="usb", ATTR{idProduct}=="0518", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="butterfly_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="061b", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="pavo" +SUBSYSTEM=="usb", ATTR{idProduct}=="051b", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="pavo_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="062b", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="petrel_pro" +SUBSYSTEM=="usb", ATTR{idProduct}=="052b", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="petrel_pro_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="062c", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="petrel_plus" +SUBSYSTEM=="usb", ATTR{idProduct}=="052c", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="petrel_plus_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="062d", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="pictor" +SUBSYSTEM=="usb", ATTR{idProduct}=="0632", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra+" +SUBSYSTEM=="usb", ATTR{idProduct}=="0532", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra+rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0633", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra+s" +SUBSYSTEM=="usb", ATTR{idProduct}=="0533", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra+s_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0634", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_petal_b" +SUBSYSTEM=="usb", ATTR{idProduct}=="0534", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_petal_b_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0635", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="femto" +SUBSYSTEM=="usb", ATTR{idProduct}=="0636", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="jarvis" +SUBSYSTEM=="usb", ATTR{idProduct}=="0536", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="jarvis_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0637", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="jarvis+s" +SUBSYSTEM=="usb", ATTR{idProduct}=="0537", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="jarvis+s_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0638", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="femto_w" +SUBSYSTEM=="usb", ATTR{idProduct}=="0639", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="argus" +SUBSYSTEM=="usb", ATTR{idProduct}=="0539", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="argus_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="063a", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="u2" +SUBSYSTEM=="usb", ATTR{idProduct}=="0650", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astradepth" +SUBSYSTEM=="usb", ATTR{idProduct}=="0651", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astradepth" +SUBSYSTEM=="usb", ATTR{idProduct}=="0654", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="qi_long_zhu" +SUBSYSTEM=="usb", ATTR{idProduct}=="0554", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="qi_long_zhu_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0655", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_plus" +SUBSYSTEM=="usb", ATTR{idProduct}=="0656", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_mini" +SUBSYSTEM=="usb", ATTR{idProduct}=="0657", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dc1" +SUBSYSTEM=="usb", ATTR{idProduct}=="0557", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dc1_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0658", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_d1" +SUBSYSTEM=="usb", ATTR{idProduct}=="0657", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dc1" +SUBSYSTEM=="usb", ATTR{idProduct}=="0557", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dc1_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="0659", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dcw" +SUBSYSTEM=="usb", ATTR{idProduct}=="0559", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dcw_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="065a", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dw" +SUBSYSTEM=="usb", ATTR{idProduct}=="065b", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra_mini_pro" +SUBSYSTEM=="usb", ATTR{idProduct}=="065c", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="gemini_e" +SUBSYSTEM=="usb", ATTR{idProduct}=="055c", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="gemini_e_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="065d", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="gemini_e_lite" +SUBSYSTEM=="usb", ATTR{idProduct}=="065e", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra_mini_s_pro" +SUBSYSTEM=="usb", ATTR{idProduct}=="0698", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="j1" +SUBSYSTEM=="usb", ATTR{idProduct}=="069c", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="TB2201" +SUBSYSTEM=="usb", ATTR{idProduct}=="06a0", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dcw2" +SUBSYSTEM=="usb", ATTR{idProduct}=="0561", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dcw2_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="069f", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_dw2" +SUBSYSTEM=="usb", ATTR{idProduct}=="069a", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_max" +SUBSYSTEM=="usb", ATTR{idProduct}=="069e", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_max_pro" +SUBSYSTEM=="usb", ATTR{idProduct}=="0560", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_max_pro_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="06aa", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_gemini_uw" +SUBSYSTEM=="usb", ATTR{idProduct}=="05aa", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="dabai_gemini_uw_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="06a6", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="gemini_ew" +SUBSYSTEM=="usb", ATTR{idProduct}=="05a6", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="gemini_ew_rgb" +SUBSYSTEM=="usb", ATTR{idProduct}=="06a7", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="gemini_ew_lite" +SUBSYSTEM=="usb", ATTR{idProduct}=="069d", ATTR{idVendor}=="2bc5", MODE:="0666", OWNER:="root", GROUP:="video", SYMLINK+="astra_pro2" diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/shared/OrbbecSDKConfig.md b/VideoProsessing/OrbbecSDK_v2.5.5/shared/OrbbecSDKConfig.md new file mode 100644 index 0000000..4a5cdd7 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/shared/OrbbecSDKConfig.md @@ -0,0 +1,208 @@ +# Orbbec SDK Configuration File Introduction (OrbbecSDKConfig_v1.0.xml) + +The Orbbec SDK configuration file is an XML file that defines the global configuration of the Orbbec SDK. + +Upon initialization, the SDK Context will load a configuration file and apply as global configurations. If you have your own configuration file, you can specify it by entering the file path when calling the Context constructor. If you don't provide a path (or input an empty string), the SDK will look for a file named `OrbbecSDKConfig.xml` in the working directory. If neither the specified file nor the default file is found, the SDK will revert to using the built-in default configuration file. + +## Log Configuration + +Log configuration mainly sets the Log level, the Log level output to the console, the Log level output to a file, configures the path for saving Log files, sets the size of each Log file, and sets the number of Log files. + +```cpp + + + + + 0 + + 0 + + 1 + + + + 100 + + 3 + + false + +``` + +## Memory Configuration + +```cpp + + + true + + 2048 + + 10 + + 10 + +``` + +**Notes** + +1. The default size of the memory pool is 2GB (2048MB). +```cpp + 2048 +``` +2. The default number of frame buffere queue in the Pipeline is 10. If the host machine processes slowly, the SDK will buffer up to 10 frames internally, which may affect the delay time in retrieving frames by the user. If the delay is significant, you can reduce the number of buffers. +```cpp + 10 +``` + +3. The default number of queues in the internal processing unit (Processing unit) is 10. If the host machine processes slowly, the SDK will buffer up to 10 frames internally, which may affect the delay time in retrieving frames by the user. If the delay is significant, you can reduce the number of queues. +```cpp + 10 +``` + +## Global Timestamp + +Based on the device's timestamp and considering data transmission delays, the timestamp is converted to the system timestamp dimension through linear regression. It can be used to synchronize timestamps of multiple different devices. The implementation plan is as follows: + +1. Global timestamp fitter: Regularly obtain the device timestamp and the current system timestamp, and calculate the fitting equation parameters using a linear regression method. +2. Global timestamp converter: Convert the data frame timestamp unit to the same unit as the device timestamp, then calculate the overflow times according to the device timestamp to convert to a 64-bit timestamp, and then convert to a global timestamp according to the fitting parameters output by the global timestamp fitter. + +```cpp + + true + + 1000 + + 100 + +``` + +1. By default, the device time is obtained every eight seconds to update the global timestamp fitter. +```cpp + 1000 +``` + +2. The default queue size of the global timestamp fitter is 10. +```cpp + 100 +``` + +**Notes** + +1. The global timestamp mainly supports the Gemini 330 series. Gemini 2, Gemini 2L, Femto Mega, and Femto Bolt are also supported but not thoroughly tested. If there are stability issues with these devices, the global timestamp function can be turned off. +```cpp + false +``` + +## Pipeline Configuration + +```cpp + + + + + + true + + + true + + + + +``` + +1. Pipeline primarily sets which video streams to enable. By default, only Depth and Color streams are enabled. You can add to enable IR streams, left IR, and right IR as follows. +```cpp + + + true + + + true + + + true + +``` + +## Device Configuration + +```cpp + + + true + + LibUVC + + + + + 0 + + + + 0 + + 5000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + +``` + +1. Set whether to enumerate network devices. Femto Mega and Gemini 2 XL support network functions. If you need to use the network functions of these two devices, you can set this to true. +```cpp + true +``` + +2. Set whether to use LibUVC or V4L2 to receive data on Linux or ARM. V4L2 is not supported by all devices, and we recommend using LibUVC. The Gemini 330 series devices support V4L2, but kernel patches are needed to obtain Metadata data. +```cpp + LibUVC +``` + +3. Set the resolution, frame rate, and data format. \ No newline at end of file diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/shared/OrbbecSDKConfig.xml b/VideoProsessing/OrbbecSDK_v2.5.5/shared/OrbbecSDKConfig.xml new file mode 100644 index 0000000..557ef96 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/shared/OrbbecSDKConfig.xml @@ -0,0 +1,2687 @@ + + + + 1.1 + + + + + 5 + + 3 + + + + 100 + + 3 + + false + + + + + true + + 2048 + + 10 + + 10 + + + + + + + + true + + + true + + false + + 1920 + + 1080 + + 30 + + MJPG + ]]> + + + + + + + + + true + + + Auto + + + + V4L2 + + + false + + 1000 + + 100 + + + + + 640 + + 576 + + 30 + + Y16 + + + + + 1920 + + 1080 + + 30 + + MJPG + + + + + 640 + + 576 + + 30 + + Y16 + + + + + + false + + 1000 + + 100 + + + + + 640 + + 576 + + 30 + + Y16 + + + + + 1920 + + 1080 + + 30 + + MJPG + + + + + 640 + + 576 + + 30 + + Y16 + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + + 640 + + 576 + + 30 + + Y16 + + + + + 1920 + + 1080 + + 30 + + MJPG + + + + + 640 + + 576 + + 30 + + Y16 + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + 1280 + + 800 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + + + + + + + + + + 640 + + 400 + + 30 + RLE + + + + 640 + + 400 + + 30 + Y8 + + + + + + + 640 + + 400 + + 15 + RLE + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y10 + + + + 1280 + + 800 + + 30 + Y10 + + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 30 + RLE + + + + 1280 + + 720 + + 30 + + MJPG + + + + 1280 + + 800 + + 30 + + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + 640 + + 400 + + 30 + RLE + + + + 640 + + 400 + + 30 + Y8 + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y10 + + + + 1280 + + 800 + + 30 + Y10 + + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + + + 800 + + 600 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 800 + + 600 + + 30 + Y8 + + + + + + 1600 + + 1200 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 800 + + 600 + + 30 + Y8 + + + + + + 640 + + 480 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 10 + RVL + + + + 1280 + + 800 + + 10 + MJPG + + + + 1280 + + 800 + + 10 + Y8 + + + + 1280 + + 800 + + 10 + Y8 + + + + + + + + + + + 1280 + + 800 + + 20 + MJPG + + + + 640 + + 400 + + 20 + RVL + + + + 640 + + 400 + + 20 + Y8 + + + + 640 + + 400 + + 20 + Y8 + + + + + + 1280 + + 800 + + 10 + MJPG + + + + 1280 + + 800 + + 10 + Y10 + + + + 1280 + + 800 + + 10 + Y10 + + + + + + 1280 + + 800 + + 10 + MJPG + + + + 1280 + + 800 + + 10 + Y8 + + + + 1280 + + 800 + + 10 + Y8 + + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + 1 + + 3000 + + 3000 + + 848 + + 480 + + 30 + Y16 + + + + 1 + + 3000 + + 3000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + 0 + + 1 + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 640 + + 400 + + 15 + Y16 + + + + 0 + + 2000 + + 2000 + + 640 + + 400 + + 15 + MJPG + + + + 640 + + 400 + + 15 + Y8 + + + + 640 + + 400 + + 15 + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + true + false + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 15 + Y16 + + + + 1280 + + 720 + + 15 + + MJPG + + + + 1280 + + 800 + + 15 + + Y8 + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 15 + Y16 + + + + 1280 + + 720 + + 15 + + MJPG + + + + 1280 + + 800 + + 15 + + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + 0 + + + 1.2 + + + true + + 1000 + + 100 + + + + + 15 + + 5000 + + 3000 + + 1280 + + 800 + + 10 + Y16 + + + + 1280 + + 800 + + 10 + MJPG + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y8 + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y8 + + 15 + + 5000 + + 3000 + + + + + 0 + + 5000 + + 3000 + + + + 0 + + 5000 + + 3000 + + + + + + + 1280 + + 800 + + 10 + MJPG + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y10 + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y10 + + 15 + + 5000 + + 3000 + + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 10 + Y12 + + + + 640 + + 480 + + 10 + MJPG + + + + 640 + + 400 + + 10 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 10 + Y12 + + + + 640 + + 400 + + 10 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 480 + + 30 + Y12 + + + + 640 + + 480 + + 30 + Y10 + + + + 640 + + 480 + + 30 + UYVY + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 480 + + 30 + Y12 + + + + 640 + + 480 + + 30 + Y10 + + + + 640 + + 480 + + 30 + UYVY + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 480 + + 30 + Y12 + + + + 640 + + 480 + + 30 + Y10 + + + + 640 + + 480 + + 30 + UYVY + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + 640 + + 480 + + 15 + MJPG + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + 640 + + 480 + + 15 + MJPG + + + + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/shared/OrbbecViewer b/VideoProsessing/OrbbecSDK_v2.5.5/shared/OrbbecViewer new file mode 100644 index 0000000..e67b9df --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/shared/OrbbecViewer @@ -0,0 +1,13 @@ +#!/bin/bash + +ORBBECSDK_PACK_NAME="OrbbecSDK" + +ORBBECSDK_PATH=/opt/OrbbecSDK_v2.5.5 + +ORBBECVIEWER_PATH=$ORBBECSDK_PATH/tools + +cd "$ORBBECVIEWER_PATH" || { echo "Unable to switch to the directory: $ORBBECVIEWER_PATH"; exit 1; } + +exec "$ORBBECVIEWER_PATH/OrbbecViewer" + +exit diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/shared/install_udev_rules.sh b/VideoProsessing/OrbbecSDK_v2.5.5/shared/install_udev_rules.sh new file mode 100644 index 0000000..0c28d83 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/shared/install_udev_rules.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# Copyright (c) Orbbec Inc. All Rights Reserved. +# Licensed under the MIT License. + +# Check if user is root/running with sudo +if [ `whoami` != root ]; then + echo Please run this script with sudo + exit +fi + +ORIG_PATH=`pwd` +cd `dirname $0` +SCRIPT_PATH=`pwd` +cd $ORIG_PATH + +if [ "`uname -s`" != "Darwin" ]; then + # Install udev rules for USB device + cp ${SCRIPT_PATH}/99-obsensor-libusb.rules /etc/udev/rules.d/99-obsensor-libusb.rules + + # resload udev rules + udevadm control --reload && udevadm trigger + + echo "usb rules file install at /etc/udev/rules.d/99-obsensor-libusb.rules" +fi +echo "exit" diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/Log/OBViewer/OBViewer_LOG20251214145141.log b/VideoProsessing/OrbbecSDK_v2.5.5/tools/Log/OBViewer/OBViewer_LOG20251214145141.log new file mode 100644 index 0000000..2bd5359 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/tools/Log/OBViewer/OBViewer_LOG20251214145141.log @@ -0,0 +1,9 @@ +2025-12-14 14:51:41.173 INFO [101363] [loggerInit@15] ********************************************** +2025-12-14 14:51:41.173 INFO [101363] [loggerInit@16] OrbbecViewer launched! Welcome!! +2025-12-14 14:51:41.174 INFO [101363] [loggerInit@17] - Version: V2.5.5 +2025-12-14 14:51:41.174 INFO [101363] [loggerInit@18] - Company: Orbbec Inc. +2025-12-14 14:51:41.174 INFO [101363] [loggerInit@19] - Website: https://www.orbbec.com/ +2025-12-14 14:51:41.174 INFO [101363] [loggerInit@20] - Documentation: +2025-12-14 14:51:41.174 INFO [101363] [loggerInit@21] ********************************************** +2025-12-14 14:51:41.174 WARN [101363] [loggerInit@22] Note: If any connected device is missing, please make sure the required permissions are enabled. +2025-12-14 14:51:41.174 WARN [101363] [loggerInit@23] More details in: https://github.com/orbbec/OrbbecSDK_v2?tab=readme-ov-file#21-environment-setup diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/Log/OBViewer/OBViewer_LOG20251214145447.log b/VideoProsessing/OrbbecSDK_v2.5.5/tools/Log/OBViewer/OBViewer_LOG20251214145447.log new file mode 100644 index 0000000..05474c8 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/tools/Log/OBViewer/OBViewer_LOG20251214145447.log @@ -0,0 +1,9 @@ +2025-12-14 14:54:47.246 INFO [101831] [loggerInit@15] ********************************************** +2025-12-14 14:54:47.247 INFO [101831] [loggerInit@16] OrbbecViewer launched! Welcome!! +2025-12-14 14:54:47.247 INFO [101831] [loggerInit@17] - Version: V2.5.5 +2025-12-14 14:54:47.247 INFO [101831] [loggerInit@18] - Company: Orbbec Inc. +2025-12-14 14:54:47.247 INFO [101831] [loggerInit@19] - Website: https://www.orbbec.com/ +2025-12-14 14:54:47.247 INFO [101831] [loggerInit@20] - Documentation: +2025-12-14 14:54:47.247 INFO [101831] [loggerInit@21] ********************************************** +2025-12-14 14:54:47.247 WARN [101831] [loggerInit@22] Note: If any connected device is missing, please make sure the required permissions are enabled. +2025-12-14 14:54:47.247 WARN [101831] [loggerInit@23] More details in: https://github.com/orbbec/OrbbecSDK_v2?tab=readme-ov-file#21-environment-setup diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/OrbbecSDK.license b/VideoProsessing/OrbbecSDK_v2.5.5/tools/OrbbecSDK.license new file mode 100644 index 0000000..5f1e5b6 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/OrbbecSDK.license differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/OrbbecSDKConfig.xml b/VideoProsessing/OrbbecSDK_v2.5.5/tools/OrbbecSDKConfig.xml new file mode 100644 index 0000000..557ef96 --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/tools/OrbbecSDKConfig.xml @@ -0,0 +1,2687 @@ + + + + 1.1 + + + + + 5 + + 3 + + + + 100 + + 3 + + false + + + + + true + + 2048 + + 10 + + 10 + + + + + + + + true + + + true + + false + + 1920 + + 1080 + + 30 + + MJPG + ]]> + + + + + + + + + true + + + Auto + + + + V4L2 + + + false + + 1000 + + 100 + + + + + 640 + + 576 + + 30 + + Y16 + + + + + 1920 + + 1080 + + 30 + + MJPG + + + + + 640 + + 576 + + 30 + + Y16 + + + + + + false + + 1000 + + 100 + + + + + 640 + + 576 + + 30 + + Y16 + + + + + 1920 + + 1080 + + 30 + + MJPG + + + + + 640 + + 576 + + 30 + + Y16 + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + + 640 + + 576 + + 30 + + Y16 + + + + + 1920 + + 1080 + + 30 + + MJPG + + + + + 640 + + 576 + + 30 + + Y16 + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + 1280 + + 800 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + + + + + + + + + + 640 + + 400 + + 30 + RLE + + + + 640 + + 400 + + 30 + Y8 + + + + + + + 640 + + 400 + + 15 + RLE + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y10 + + + + 1280 + + 800 + + 30 + Y10 + + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 30 + RLE + + + + 1280 + + 720 + + 30 + + MJPG + + + + 1280 + + 800 + + 30 + + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + 640 + + 400 + + 30 + RLE + + + + 640 + + 400 + + 30 + Y8 + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y10 + + + + 1280 + + 800 + + 30 + Y10 + + + + + + + 1920 + + 1080 + + 30 + MJPG + + + + 1280 + + 800 + + 30 + Y8 + + + + 1280 + + 800 + + 30 + Y8 + + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + + + 800 + + 600 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 800 + + 600 + + 30 + Y8 + + + + + + 1600 + + 1200 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 800 + + 600 + + 30 + Y8 + + + + + + 640 + + 480 + + 30 + RLE + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 10 + RVL + + + + 1280 + + 800 + + 10 + MJPG + + + + 1280 + + 800 + + 10 + Y8 + + + + 1280 + + 800 + + 10 + Y8 + + + + + + + + + + + 1280 + + 800 + + 20 + MJPG + + + + 640 + + 400 + + 20 + RVL + + + + 640 + + 400 + + 20 + Y8 + + + + 640 + + 400 + + 20 + Y8 + + + + + + 1280 + + 800 + + 10 + MJPG + + + + 1280 + + 800 + + 10 + Y10 + + + + 1280 + + 800 + + 10 + Y10 + + + + + + 1280 + + 800 + + 10 + MJPG + + + + 1280 + + 800 + + 10 + Y8 + + + + 1280 + + 800 + + 10 + Y8 + + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + 1 + + 3000 + + 3000 + + 848 + + 480 + + 30 + Y16 + + + + 1 + + 3000 + + 3000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + 0 + + 1 + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 640 + + 400 + + 15 + Y16 + + + + 0 + + 2000 + + 2000 + + 640 + + 400 + + 15 + MJPG + + + + 640 + + 400 + + 15 + Y8 + + + + 640 + + 400 + + 15 + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + true + false + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + true + + 1000 + + 100 + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + true + + 1000 + + 100 + + + false + true + + + + 0 + + 1 + + + + 640 + + 480 + + 30 + Y16 + + + + 1280 + + 720 + + 30 + MJPG + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + 640 + + 480 + + 30 + Y8 + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 15 + Y16 + + + + 1280 + + 720 + + 15 + + MJPG + + + + 1280 + + 800 + + 15 + + Y8 + + + + + + LibUVC + + + false + + 1000 + + 100 + + + + 0 + + + + 1280 + + 800 + + 15 + Y16 + + + + 1280 + + 720 + + 15 + + MJPG + + + + 1280 + + 800 + + 15 + + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + false + true + + + + true + + 1000 + + 100 + + + + 0 + + 1 + + + + + + 0 + + 2000 + + 2000 + + 848 + + 480 + + 30 + Y16 + + + + 0 + + 2000 + + 2000 + + 1280 + + 720 + + 30 + MJPG + + + + 848 + + 480 + + 30 + Y8 + + + + 848 + + 480 + + 30 + Y8 + + + + + + + LibUVC + + + 0 + + + 1.2 + + + true + + 1000 + + 100 + + + + + 15 + + 5000 + + 3000 + + 1280 + + 800 + + 10 + Y16 + + + + 1280 + + 800 + + 10 + MJPG + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y8 + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y8 + + 15 + + 5000 + + 3000 + + + + + 0 + + 5000 + + 3000 + + + + 0 + + 5000 + + 3000 + + + + + + + 1280 + + 800 + + 10 + MJPG + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y10 + + 15 + + 5000 + + 3000 + + + + 1280 + + 800 + + 10 + Y10 + + 15 + + 5000 + + 3000 + + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 10 + Y12 + + + + 640 + + 480 + + 10 + MJPG + + + + 640 + + 400 + + 10 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 10 + Y12 + + + + 640 + + 400 + + 10 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 480 + + 30 + Y12 + + + + 640 + + 480 + + 30 + Y10 + + + + 640 + + 480 + + 30 + UYVY + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 480 + + 30 + Y12 + + + + 640 + + 480 + + 30 + Y10 + + + + 640 + + 480 + + 30 + UYVY + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 480 + + 30 + Y12 + + + + 640 + + 480 + + 30 + Y10 + + + + 640 + + 480 + + 30 + UYVY + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + 640 + + 480 + + 15 + MJPG + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + + LibUVC + + + true + + + + 1 + + + + + 640 + + 400 + + 15 + Y12 + + + + 640 + + 400 + + 15 + Y10 + + + + 640 + + 480 + + 15 + MJPG + + + + diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/OrbbecViewer b/VideoProsessing/OrbbecSDK_v2.5.5/tools/OrbbecViewer new file mode 100644 index 0000000..e5b2585 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/OrbbecViewer differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/config/config.ini b/VideoProsessing/OrbbecSDK_v2.5.5/tools/config/config.ini new file mode 100644 index 0000000..3366b9d --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/tools/config/config.ini @@ -0,0 +1,53 @@ +[Decoder] +hw_accele_enable=0 + +[Storage] +path=./output + +[Color] +enable=0 +width=1080 +height=1920 +fps=60 +format=MJPG + +[Depth] +enable=0 +width=960 +height=1280 +fps=30 + +[IR] +enable=false + +[RawPhase] +[Property] +sync=true +watch=false + +[Config] +AutoConnect=true +SaveFirmwareLog=true +NetDeviceIP=192.168.1.10 +RefreshRate=30 +MaxDepthDistance=12000 +IsDeviceClockSyncFirstConnected=true +savePngWithExposureGain=false + +; -- OrbbecViewer language. If no config option, UI language is followed system language. +;english=true +isTriggerRenderMode=false + +; Whether to show low-FPS warnings in trigger mode (software/hardware) +allowLowFpsWarningInTrigger=false + +[Curl] +geturl=https://vcp.developer.orbbec.com.cn/experience_api/v1/algorithm/list +posturl=https://vcp.developer.orbbec.com.cn/experience_api/ +getFirmwareListUrl=http://159.27.233.135:8080/api/firmware/queryAll +FirmwareUrl=http://app.orbbec.com:8080/ +[OBViewerLog] +WriteToFile=true +FileDir=./Log/OBViewer/ +MaxFileSize=2048000000000 +MaxFiles=10 diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/config/view.ini b/VideoProsessing/OrbbecSDK_v2.5.5/tools/config/view.ini new file mode 100644 index 0000000..23e06ca --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/tools/config/view.ini @@ -0,0 +1,8 @@ +[View] +ctrl_dock_fold=false +current_tab_index=1 +show_log_console=true + +[window] +width=1361 +height=711 diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/depthengine/libdepthengine.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/depthengine/libdepthengine.so new file mode 100644 index 0000000..61c3e38 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/depthengine/libdepthengine.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/depthengine/libdepthengine.so.2.0 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/depthengine/libdepthengine.so.2.0 new file mode 100644 index 0000000..61c3e38 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/depthengine/libdepthengine.so.2.0 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/filters/libFilterProcessor.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/filters/libFilterProcessor.so new file mode 100644 index 0000000..309f7a9 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/filters/libFilterProcessor.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/filters/libob_priv_filter.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/filters/libob_priv_filter.so new file mode 100644 index 0000000..1f63a27 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/filters/libob_priv_filter.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/firmwareupdater/libfirmwareupdater.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/firmwareupdater/libfirmwareupdater.so new file mode 100644 index 0000000..4a89a3d Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/firmwareupdater/libfirmwareupdater.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/frameprocessor/libob_frame_processor.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/frameprocessor/libob_frame_processor.so new file mode 100644 index 0000000..7d5cf10 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/extensions/frameprocessor/libob_frame_processor.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libGLEW.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libGLEW.so new file mode 100644 index 0000000..16fc2cd Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libGLEW.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libGLEW.so.2.0 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libGLEW.so.2.0 new file mode 100644 index 0000000..16fc2cd Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libGLEW.so.2.0 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libGLEW.so.2.0.0 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libGLEW.so.2.0.0 new file mode 100644 index 0000000..16fc2cd Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libGLEW.so.2.0.0 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libOrbbecSDK.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libOrbbecSDK.so new file mode 100644 index 0000000..434cd68 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libOrbbecSDK.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libOrbbecSDK.so.2 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libOrbbecSDK.so.2 new file mode 100644 index 0000000..434cd68 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libOrbbecSDK.so.2 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libOrbbecSDK.so.2.5.5 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libOrbbecSDK.so.2.5.5 new file mode 100644 index 0000000..434cd68 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libOrbbecSDK.so.2.5.5 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavcodec.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavcodec.so new file mode 100644 index 0000000..965605b Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavcodec.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavcodec.so.58 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavcodec.so.58 new file mode 100644 index 0000000..965605b Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavcodec.so.58 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavcodec.so.58.54.100 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavcodec.so.58.54.100 new file mode 100644 index 0000000..965605b Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavcodec.so.58.54.100 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavdevice.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavdevice.so new file mode 100644 index 0000000..f001912 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavdevice.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavdevice.so.58 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavdevice.so.58 new file mode 100644 index 0000000..f001912 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavdevice.so.58 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavdevice.so.58.8.100 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavdevice.so.58.8.100 new file mode 100644 index 0000000..f001912 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavdevice.so.58.8.100 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavfilter.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavfilter.so new file mode 100644 index 0000000..8c8fda3 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavfilter.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavfilter.so.7 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavfilter.so.7 new file mode 100644 index 0000000..6740add Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavfilter.so.7 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavfilter.so.7.57.100 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavfilter.so.7.57.100 new file mode 100644 index 0000000..6740add Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavfilter.so.7.57.100 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavformat.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavformat.so new file mode 100644 index 0000000..65933b8 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavformat.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavformat.so.58 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavformat.so.58 new file mode 100644 index 0000000..65933b8 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavformat.so.58 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavformat.so.58.29.100 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavformat.so.58.29.100 new file mode 100644 index 0000000..65933b8 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavformat.so.58.29.100 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavutil.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavutil.so new file mode 100644 index 0000000..e4ff702 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavutil.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavutil.so.56 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavutil.so.56 new file mode 100644 index 0000000..e4ff702 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavutil.so.56 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavutil.so.56.31.100 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavutil.so.56.31.100 new file mode 100644 index 0000000..e4ff702 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libavutil.so.56.31.100 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcrypto.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcrypto.so new file mode 100644 index 0000000..3f88422 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcrypto.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcrypto.so.1.1 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcrypto.so.1.1 new file mode 100644 index 0000000..3f88422 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcrypto.so.1.1 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcurl.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcurl.so new file mode 100644 index 0000000..64fcf4e Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcurl.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcurl.so.4 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcurl.so.4 new file mode 100644 index 0000000..64fcf4e Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcurl.so.4 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcurl.so.4.8.0 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcurl.so.4.8.0 new file mode 100644 index 0000000..64fcf4e Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libcurl.so.4.8.0 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libglfw.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libglfw.so new file mode 100644 index 0000000..5a5e2db Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libglfw.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libglfw.so.3 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libglfw.so.3 new file mode 100644 index 0000000..5a5e2db Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libglfw.so.3 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libglfw.so.3.3 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libglfw.so.3.3 new file mode 100644 index 0000000..5a5e2db Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libglfw.so.3.3 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libjsoncpp.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libjsoncpp.so new file mode 100644 index 0000000..955e6e6 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libjsoncpp.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libobpng.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libobpng.so new file mode 100644 index 0000000..40a7572 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libobpng.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libpostproc.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libpostproc.so new file mode 100644 index 0000000..95bbf0c Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libpostproc.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libpostproc.so.55 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libpostproc.so.55 new file mode 100644 index 0000000..95bbf0c Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libpostproc.so.55 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libpostproc.so.55.5.100 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libpostproc.so.55.5.100 new file mode 100644 index 0000000..95bbf0c Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libpostproc.so.55.5.100 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libserial.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libserial.so new file mode 100644 index 0000000..18d58f8 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libserial.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libssl.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libssl.so new file mode 100644 index 0000000..9c644df Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libssl.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libssl.so.1.1 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libssl.so.1.1 new file mode 100644 index 0000000..9c644df Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libssl.so.1.1 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswresample.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswresample.so new file mode 100644 index 0000000..70802f2 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswresample.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswresample.so.3 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswresample.so.3 new file mode 100644 index 0000000..70802f2 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswresample.so.3 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswresample.so.3.5.100 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswresample.so.3.5.100 new file mode 100644 index 0000000..70802f2 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswresample.so.3.5.100 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswscale.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswscale.so new file mode 100644 index 0000000..d6082bc Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswscale.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswscale.so.5 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswscale.so.5 new file mode 100644 index 0000000..d6082bc Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswscale.so.5 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswscale.so.5.5.100 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswscale.so.5.5.100 new file mode 100644 index 0000000..d6082bc Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libswscale.so.5.5.100 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libx264.so b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libx264.so new file mode 100644 index 0000000..c96e415 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libx264.so differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/libx264.so.164 b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libx264.so.164 new file mode 100644 index 0000000..c96e415 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/libx264.so.164 differ diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/nvmflash.sh b/VideoProsessing/OrbbecSDK_v2.5.5/tools/nvmflash.sh new file mode 100644 index 0000000..75954da --- /dev/null +++ b/VideoProsessing/OrbbecSDK_v2.5.5/tools/nvmflash.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +# Copyright (c) 2018-2021, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +curdir=$(cd $1 && pwd); +echo $curdir +showlogs=0; +if [ "$1" = "--showlogs" ]; then + showlogs=1; +fi; + +# Find devices to flash +devpaths=($(find /sys/bus/usb/devices/usb*/ \ + -name devnum -print0 | { + found=() + while read -r -d "" fn_devnum; do + dir="$(dirname "${fn_devnum}")" + vendor="$(cat "${dir}/idVendor")" + if [ "${vendor}" != "0955" ]; then + continue + fi + product="$(cat "${dir}/idProduct")" + case "${product}" in + "7721") ;; + "7f21") ;; + "7018") ;; + "7c18") ;; + "7121") ;; + "7019") ;; + "7819") ;; + "7e19") ;; + "7418") ;; + *) + continue + ;; + esac + fn_busnum="${dir}/busnum" + if [ ! -f "${fn_busnum}" ]; then + continue + fi + fn_devpath="${dir}/devpath" + if [ ! -f "${fn_devpath}" ]; then + continue + fi + busnum="$(cat "${fn_busnum}")" + devpath="$(cat "${fn_devpath}")" + found+=("${busnum}-${devpath}") + done + echo "${found[@]}" +})) + +# Exit if no devices to flash +if [ ${#devpaths[@]} -eq 0 ]; then + echo "No devices to flash" + exit 1 +fi + +# Create a folder for saving log +mkdir -p mfilogs; +pid="$$" +ts=`date +%Y%m%d-%H%M%S`; + +# Clear old gpt crufts +rm -f mbr_* gpt_*; + +# Flash all devices in background +flash_pids=() +for devpath in "${devpaths[@]}"; do + fn_log="mfilogs/${ts}_${pid}_flash_${devpath}.log" + cmd="${curdir}/nvaflash.sh ${devpath}"; + ${cmd} > "${fn_log}" 2>&1 & + flash_pid="$!"; + flash_pids+=("${flash_pid}") + echo "Start flashing device: ${devpath}, PID: ${flash_pid}"; + if [ ${showlogs} -eq 1 ]; then + gnome-terminal -e "tail -f ${fn_log}" -t ${fn_log} > /dev/null 2>&1 & + fi; +done + +# Wait until all flash processes done +failure=0 +while true; do + running=0 + if [ ${showlogs} -ne 1 ]; then + echo -n "Ongoing processes:" + fi; + new_flash_pids=() + for flash_pid in "${flash_pids[@]}"; do + if [ -e "/proc/${flash_pid}" ]; then + if [ ${showlogs} -ne 1 ]; then + echo -n " ${flash_pid}" + fi; + running=$((${running} + 1)) + new_flash_pids+=("${flash_pid}") + else + wait "${flash_pid}" || failure=1 + fi + done + if [ ${showlogs} -ne 1 ]; then + echo + fi; + if [ ${running} -eq 0 ]; then + break + fi + flash_pids=("${new_flash_pids[@]}") + sleep 5 +done + +if [ ${failure} -ne 0 ]; then + echo "Flash complete (WITH FAILURES)"; + exit 1 +fi + +echo "Flash complete (SUCCESS)" diff --git a/VideoProsessing/OrbbecSDK_v2.5.5/tools/obpng_console b/VideoProsessing/OrbbecSDK_v2.5.5/tools/obpng_console new file mode 100644 index 0000000..e890ba0 Binary files /dev/null and b/VideoProsessing/OrbbecSDK_v2.5.5/tools/obpng_console differ diff --git a/VideoProsessing/bin/video b/VideoProsessing/bin/video new file mode 100755 index 0000000..3078042 Binary files /dev/null and b/VideoProsessing/bin/video differ diff --git a/VideoProsessing/src/main.cpp b/VideoProsessing/src/main.cpp new file mode 100644 index 0000000..830229a --- /dev/null +++ b/VideoProsessing/src/main.cpp @@ -0,0 +1,973 @@ +/* +本程序用于视频分流 +1.推流摄像头画面,使用UDP原生协议进行推流,交由YOLO模型进行处理 +2.接收YOLO传来的坐标和深度数据 +3.根据获取到的数据绘制边框和相应数据 +4.根据距离信息进行报警和图片视频保存 +5.输出处理完毕的视频帧 +*/ + +#include +#include +#include +#include +#include +#include +#include //队列 +#include +#include +#include //双端队列 +#include +#include "Netra.hpp" +#include +#include + +using namespace std; +using namespace QCL; +using namespace cv; +using namespace chrono_literals; +namespace bp = boost::process; + +// 路径和接口 +const string mqtt_url = "tcp://127.0.0.1:1883"; +const string clientId = "video_subData"; +const string Topic = "/video/PersonData"; +const string filePath = "/home/orangepi/RKApp/InitAuth/conf/.env"; // 配置保存路径 +const string warningPath = "/mnt/save/warning/"; // 报警图片保存路径 +const string videoPath = "/mnt/save/video/"; // 报警视频保存路径 + +// 保存检测结果 +struct Dection +{ + double x, y, w, h; // 支持归一化(0~1)或像素值 + double distance; +}; + +// 保存报警距离 +struct dangerDistance +{ + int danger; + int warn; + int safe; +} dis; + +struct Point2N +{ + double x, y; +}; // 归一化坐标 +struct ZoneBox +{ + string name; + array vertices; // 存 0..1 +}; + +// 报警框坐标 +ZoneBox g_safe, g_warn, g_dang; + +// 全局变量和对象 +VideoCapture cap; // 在 videoInit 中用 V4L2 打开 +Mat handleFrame; // 存放处理后的帧 +const int Qos = 0; +mqtt::async_client client(mqtt_url, clientId); +mutex detMutex; // 保护latestDection的互斥锁 +vector latestDection; // 保存最新接收到的检测结果 +mutex alertMutex; // 保护alertQueue的互斥锁 +condition_variable alertcv; // 通知报警线程有新任务 +queue alertQueue; // 存放解析后的数据 +std::atomic alertWorkerRunning{false}; // 工作线程运行标志 +atomic outPutMode = false; // 保存报警输出false--电平 +bool mainRunning = true; + +// 视频相关 +const int FPS = 30; // 帧率 +const int PRE_RECORD_SECONDS = 10; // 预录制时长 +const int MAX_BUFFER_SIZE = FPS * PRE_RECORD_SECONDS; // 缓冲区最大帧数 +mutex bufferMutex; // 保护缓冲区的锁 +deque videoDeque; // 环形缓冲区,存储最近十秒的画面帧 +atomic isRecording{false}; // 是否正在写入文件 +atomic mediaMirror{false}; // 是否镜像 +atomic mediaFlip{false}; // 是否翻转 + +// 用于报警线程的“最新一帧检测结果”(丢弃旧帧,保证低延迟) +static std::mutex latestAlertMutex; +static std::condition_variable latestAlertCv; +static std::optional> latestAlertDets; // 仅存最新 +static std::atomic latestAlertSeq{0}; + +// 把阈值/输出模式缓存到内存,避免频繁读文件 +struct RuntimeConfig +{ + std::atomic danger{0}; + std::atomic warn{0}; + std::atomic safe{0}; + std::atomic outPutMode{false}; +}; +static RuntimeConfig g_cfg; + +// mqtt初始化 +void MqttInit(); +// 摄像头管道初始化 +bool videoInit(VideoCapture &cap); +// ffmpeg管道初始化 +FILE *pipeInit(); +// 对单个帧进行处理 +bool processFrame(VideoCapture &cap, FILE *pipe, Mat &frame, int64 &count, chrono::steady_clock::time_point &t0); +// 资源清理 +void cleanup(FILE *pipe, VideoCapture &cap); +// 主循环 +void mainLoop(VideoCapture &cap, FILE *pipe); +// mqtt接收订阅消息的回调 +void getMsgCallback(mqtt::const_message_ptr msg); +// 绘制矩形方框和深度信息 +void drawRect(double x, double y, double w, double h, double distance); +// 报警线程 +void warnThread(); +// 获取报警距离和输出模式 +bool GetDistance(); +// 调用报警输出程序 +void setGPIOLevel(int level); +// 获取当前时间字符串做文件名 +string getCurrentTimeStr(); +// 保存图片 +void saveAlarmImage(const cv::Mat &frame); +// 保存视频 +void saveAlarmVideo(std::deque bufferSnapshot); + +// 从配置文件读取 zones(SAFE/WARN/DANG 的 4 点,归一化值) +bool LoadZonesFromEnv(); + +// 绘制 zones 多边形(将归一化坐标按当前帧尺寸缩放) +void drawZones(Mat &img); + +// 检测框底边是否接触 danger 多边形(用缩放后的像素点) +bool bottomTouchesDanger(const Dection &d, const ZoneBox &dangerBox); + +// 读取配置,获取翻转镜像设置 +void loadMirrerSet(); + +// 进行镜像和翻转 +void SetMirror(Mat &frame); + +// 只在 .env 文件发生修改后才重新加载 zones 和 镜像配置 +void ReloadConfigIfChanged(); + +// 退出信号 +void Exit(int sig); + +int main() +{ + // 确保服务器启动 + this_thread::sleep_for(5s); + + // 初始化摄像头 + if (!videoInit(cap)) + { + return -1; + } + + // 初始化FFmpeg管道 + FILE *pipe = pipeInit(); + if (!pipe) + { + return -1; + } + + // 先加载一次区域,避免首帧没有框 + LoadZonesFromEnv(); + + signal(SIGINT, Exit); + + // 初始化mqtt:订阅,回调函数 + MqttInit(); + + // 主处理循环 + mainLoop(cap, pipe); + + // 清理资源 + cleanup(pipe, cap); + + return 0; +} + +// 只在 .env 文件发生修改后才重新加载 zones 和 镜像配置 +void ReloadConfigIfChanged() +{ + static std::filesystem::file_time_type lastEnvWriteTime{}; + static bool first = true; + + std::error_code ec; + auto curWriteTime = std::filesystem::last_write_time(filePath, ec); + if (ec) + { + // 获取失败就跳过,避免频繁输出 + return; + } + + // 首次调用:直接加载一次并记录时间 + if (first) + { + first = false; + lastEnvWriteTime = curWriteTime; + LoadZonesFromEnv(); + loadMirrerSet(); + return; + } + + // 文件未修改则直接返回 + if (curWriteTime <= lastEnvWriteTime) + { + return; + } + + // 文件有更新:重新读取配置 + lastEnvWriteTime = curWriteTime; + LoadZonesFromEnv(); + loadMirrerSet(); +} + +// 进行镜像和翻转 +void SetMirror(Mat &frame) +{ + bool mirror = mediaMirror.load(); + bool flipV = mediaFlip.load(); + + if (mirror && flipV) + { // 同时镜像和翻转 + cv::flip(frame, frame, -1); + } + else if (mirror) + { // 只镜像 + cv::flip(frame, frame, 1); + } + else if (flipV) + { // 只翻转 + cv::flip(frame, frame, 0); + } +} + +// 读取配置,获取翻转镜像设置 +void loadMirrerSet() +{ + ReadFile rf(filePath); + if (!rf.Open()) + { + cerr << "文件打开失败" << endl; + return; + } + else + { // 解析文件 + auto lines = rf.ReadLines(); + rf.Close(); + auto getBool([&](const string &key, bool &out) + { + out = false; + for(auto&line:lines) + { + if(line.rfind(key+"=",0)==0) + { + // 兼容大小写与可能的空格 + auto val = line.substr(key.size() + 1); + for (auto &c : val) c = ::tolower(c); + val.erase(remove_if(val.begin(), val.end(), ::isspace), val.end()); + if(val=="true") + { + out = true; + } + return out; + } + } + return out; }); + + bool mirror = false, flip = false; + + getBool("MEDIA_MIRROR", mirror); + getBool("MEDIA_FLIP", flip); + + mediaMirror.store(mirror); + mediaFlip.store(flip); + } +} + +// 退出信号 +void Exit(int sig) +{ + cout << "Exiting....." << endl; + // 停止主循环与报警线程 + mainRunning = false; + alertWorkerRunning = false; + // 唤醒报警线程从 wait/ wait_for 中返回 + latestAlertCv.notify_all(); + alertcv.notify_all(); +} + +// 检测框底边是否接触 danger 多边形(用缩放后的像素点) +bool bottomTouchesDanger(const Dection &d, const ZoneBox &dangerBox) +{ + vector poly; + poly.reserve(4); + for (auto &p : dangerBox.vertices) + { + poly.emplace_back(static_cast(p.x * handleFrame.cols), + static_cast(p.y * handleFrame.rows)); + } + + auto toPixX = [&](double v) -> int + { + return (v <= 1.0) ? static_cast(v * handleFrame.cols) : static_cast(v); + }; + auto toPixY = [&](double v) -> int + { + return (v <= 1.0) ? static_cast(v * handleFrame.rows) : static_cast(v); + }; + + int x0 = toPixX(d.x); + int y0 = toPixY(d.y); + int wpx = toPixX(d.w); + int hpx = toPixY(d.h); + + int x1 = x0 + wpx; + int yb = y0 + hpx; + + int samples = max(5, wpx / 20); + for (int i = 0; i <= samples; ++i) + { + int x = x0 + (i * (x1 - x0)) / samples; + Point pt(x, yb); + double res = pointPolygonTest(poly, pt, false); + if (res >= 0) + return true; + } + return false; +} + +// 从配置文件读取 zones(SAFE/WARN/DANG 的 4 点,归一化值) +bool LoadZonesFromEnv() +{ + ReadFile rf(filePath); + if (!rf.Open()) + { + cerr << "文件打开失败: " << filePath << endl; + return false; + } + auto lines = rf.ReadLines(); + rf.Close(); + + auto getDouble = [&](const string &key, double &out) + { + for (auto &line : lines) + { + if (line.rfind(key + "=", 0) == 0) + { + try + { + out = stod(line.substr(key.size() + 1)); + return true; + } + catch (...) + { + return false; + } + } + } + return false; + }; + + auto loadBox = [&](ZoneBox &box, const string &prefix) + { + box.name = prefix; + for (int i = 0; i < 4; ++i) + { + double x = 0.0, y = 0.0; + getDouble(QCL::format("{}_{}_X", prefix, i + 1), x); + getDouble(QCL::format("{}_{}_Y", prefix, i + 1), y); + box.vertices[i] = {x, y}; // 归一化值 + } + }; + + loadBox(g_safe, "SAFE"); + loadBox(g_warn, "WARN"); + loadBox(g_dang, "DANG"); + return true; +} + +// 绘制 zones 多边形(将归一化坐标按当前帧尺寸缩放) +void drawZones(Mat &img) +{ + auto drawPoly = [&](const ZoneBox &box, const Scalar &color) + { + vector pts; + pts.reserve(4); + for (auto &p : box.vertices) + { + int px = static_cast(p.x * img.cols); + int py = static_cast(p.y * img.rows); + pts.emplace_back(px, py); + } + polylines(img, pts, true, color, 2); + }; + drawPoly(g_safe, Scalar(0, 255, 0)); // 绿色 + drawPoly(g_warn, Scalar(0, 255, 255)); // 黄色 + drawPoly(g_dang, Scalar(0, 0, 255)); // 红色 +} + +// 保存图片 +void saveAlarmImage(const Mat &frame) +{ + if (frame.empty()) + { + cerr << "报警图片保存跳过: 帧为空" << endl; + return; + } + string fileName = warningPath + "alarm_" + getCurrentTimeStr() + ".jpg"; + cout << "imgpath = " << fileName << endl; + if (!imwrite(fileName, frame)) + cerr << "图片保存失败" << endl; +} + +// 保存视频 +void saveAlarmVideo(deque bufferSnapshot) +{ + if (bufferSnapshot.empty() || bufferSnapshot.front().empty()) + { + cerr << "报警视频保存跳过: 缓冲为空" << endl; + return; + } + + thread([bufferSnapshot]() + { + string fileName = videoPath + "alarm_" + getCurrentTimeStr() + ".mp4"; + VideoWriter write; + int codec = write.fourcc('H', '2', '6', '4'); + Size size = bufferSnapshot.front().size(); + bool color = bufferSnapshot.front().channels() == 3; + + if (!write.open(fileName, codec, FPS, size, color)) + { + cerr << "视频文件打开失败: " << fileName << endl; + return; + } + + for (auto &ii : bufferSnapshot) + { + if (!ii.empty()) + write.write(ii); + } + write.release(); }) + .detach(); +} + +// 获取当前时间字符串做文件名 +string getCurrentTimeStr() +{ + auto now = chrono::system_clock::now(); + auto time_t_now = chrono::system_clock::to_time_t(now); + stringstream ss; + ss << put_time(localtime(&time_t_now), "%Y%m%d_%H%M%S"); + return ss.str(); +} + +// 调用报警输出程序 +void setGPIOLevel(int level) +{ + string cmd = "echo 'orangepi' | sudo -S /home/orangepi/RKApp/GPIOSignal/bin/sendGpioSignal " + to_string(level); + system(cmd.c_str()); +} + +static bool RefreshDistanceConfig() +{ + ReadFile rf(filePath); + if (!rf.Open()) + return false; + + auto lines = rf.ReadLines(); + rf.Close(); + + int danger = g_cfg.danger.load(); + int warn = g_cfg.warn.load(); + int safe = g_cfg.safe.load(); + bool opm = g_cfg.outPutMode.load(); + + for (auto &line : lines) + { + if (line.find("NEAR_THRESHOLD=") != string::npos) + danger = stoi(line.substr(sizeof("NEAR_THRESHOLD=") - 1)); + else if (line.find("MID_THRESHOLD=") != string::npos) + warn = stoi(line.substr(sizeof("MID_THRESHOLD=") - 1)); + else if (line.find("MAX_DISTANCE=") != string::npos) + safe = stoi(line.substr(sizeof("MAX_DISTANCE=") - 1)); + else if (line.find("outPutMode:") != string::npos) + { + // 注意:你这里的 key/value 格式看起来不像 ENV 的 KEY=VALUE,确认一下 + string val = line.substr(sizeof("outPutMode:")); + opm = (val == "true"); + } + } + + g_cfg.danger.store(danger); + g_cfg.warn.store(warn); + g_cfg.safe.store(safe); + g_cfg.outPutMode.store(opm); + return true; +} + +// 报警线程 +void warnThread() +{ + thread([]() + { + bool isAlarming = false; + auto lastDangerTime = chrono::steady_clock::now(); + + // 初次加载配置 + RefreshDistanceConfig(); + + // 初次设置电平 + int normalLevel = g_cfg.outPutMode.load() ? 0 : 1; + int alarmLevel = g_cfg.outPutMode.load() ? 1 : 0; + setGPIOLevel(normalLevel); + + // 配置低频刷新(例如 1s 一次),避免每帧 IO + auto lastCfgRefresh = chrono::steady_clock::now(); + + uint64_t seenSeq = latestAlertSeq.load(); + + while (alertWorkerRunning.load()) + { + // 等待新数据到来(带超时:即使YOLO不发消息,也能走“离开后2秒恢复”) + std::unique_lock lk(latestAlertMutex); + bool gotNew = latestAlertCv.wait_for(lk, std::chrono::milliseconds(50), [&] { + return !alertWorkerRunning.load() || latestAlertSeq.load() != seenSeq; + }); + + if (!alertWorkerRunning.load()) + break; + + // 低频刷新配置(避免抖动) + auto now = chrono::steady_clock::now(); + if (now - lastCfgRefresh >= chrono::seconds(1)) + { + RefreshDistanceConfig(); + lastCfgRefresh = now; + + normalLevel = g_cfg.outPutMode.load() ? 0 : 1; + alarmLevel = g_cfg.outPutMode.load() ? 1 : 0; + } + + // 如果超时没收到新消息:视为“当前无检测=离开危险区” + if (!gotNew) + { + if (isAlarming) + { + auto dur = chrono::duration_cast( + chrono::steady_clock::now() - lastDangerTime).count(); + if (dur >= 2000) + { + isAlarming = false; + setGPIOLevel(normalLevel); + } + } + continue; + } + + // 有新消息:取最新检测 + seenSeq = latestAlertSeq.load(); + auto detsOpt = latestAlertDets; + lk.unlock(); + + if (!detsOpt.has_value()) + continue; + + bool currentFrameHasDanger = false; + const int dangerTh = g_cfg.danger.load(); + + // 判定是否危险(距离优先,否则用多边形) + for (const auto &d : detsOpt.value()) + { + if (d.distance > 0.0 && d.distance <= dangerTh) + { + currentFrameHasDanger = true; + break; + } + if (d.distance == 0.0) + { + if (bottomTouchesDanger(d, g_dang)) + { + currentFrameHasDanger = true; + break; + } + } + } + + // 状态机:危险->保持报警;不危险->延迟2秒恢复 + if (currentFrameHasDanger) + { + lastDangerTime = chrono::steady_clock::now(); + if (!isAlarming) + { + isAlarming = true; + setGPIOLevel(alarmLevel); + + // 保存媒体(你原逻辑保留) + { + lock_guard lk2(bufferMutex); + Mat framToSave; + deque bufferToSave; + if (!videoDeque.empty()) + { + framToSave = videoDeque.back().clone(); + bufferToSave = videoDeque; + } + else if (!handleFrame.empty()) + { + framToSave = handleFrame.clone(); + } + + if (!framToSave.empty()) + saveAlarmImage(framToSave); + if (!bufferToSave.empty()) + saveAlarmVideo(bufferToSave); + } + } + } + else + { + if (isAlarming) + { + auto dur = chrono::duration_cast( + chrono::steady_clock::now() - lastDangerTime).count(); + if (dur >= 2000) + { + isAlarming = false; + setGPIOLevel(normalLevel); + } + } + } + } }) + .detach(); +} + +// 获取报警距离 +bool GetDistance() +{ + // 获取距离信息 + ReadFile rf(filePath); + if (rf.Open() == false) + { + cerr << "文件打开失败" << endl; + return false; + } + + auto lines = rf.ReadLines(); + string str; + for (auto &line : lines) + { + if (line.find("NEAR_THRESHOLD=") != string::npos) + dis.danger = stoi(line.substr(sizeof("NEAR_THRESHOLD=") - 1)); + else if (line.find("MID_THRESHOLD=") != string::npos) + dis.warn = stoi(line.substr(sizeof("MID_THRESHOLD=") - 1)); + else if (line.find("MAX_DISTANCE=") != string::npos) + dis.safe = stoi(line.substr(sizeof("MAX_DISTANCE=") - 1)); + else if (line.find("outPutMode:") != string::npos) + { + // 确认输电平模式 + string val = line.substr(sizeof("outPutMode:")); + outPutMode = (val == "true"); + } + } + + rf.Close(); + + return true; +} + +// 绘制矩形方框和深度信息 +void drawRect(double x, double y, double w, double h, double distance) +{ + // 将归一化(0~1)或像素值统一转换为像素 + auto toPixX = [&](double v) -> int + { + return (v <= 1.0) ? static_cast(v * handleFrame.cols) : static_cast(v); + }; + auto toPixY = [&](double v) -> int + { + return (v <= 1.0) ? static_cast(v * handleFrame.rows) : static_cast(v); + }; + + int px = toPixX(x); + int py = toPixY(y); + int pw = toPixX(w); + int ph = toPixY(h); + + // 边界裁剪,避免越界 + px = std::max(0, std::min(px, handleFrame.cols - 1)); + py = std::max(0, std::min(py, handleFrame.rows - 1)); + pw = std::max(1, std::min(pw, handleFrame.cols - px)); + ph = std::max(1, std::min(ph, handleFrame.rows - py)); + + Rect r(px, py, pw, ph); + + Scalar sca(0, 255, 0); + if (!GetDistance()) + { + sca = Scalar(0, 0, 0); + } + else if (distance <= dis.danger) + sca = Scalar(0, 0, 255); + else if (distance <= dis.warn) + sca = Scalar(0, 255, 255); + + rectangle(handleFrame, r, sca, 2); + putText(handleFrame, to_string(distance), Point(px, py), FONT_HERSHEY_SIMPLEX, 0.35, Scalar(0, 0, 0)); +} + +// mqtt初始化 +void MqttInit() +{ + // 设置回调 + client.set_connected_handler([](const string &cause) + { cout << "连接成功" << endl; }); + + client.set_message_callback(getMsgCallback); + + client.connect()->wait(); + client.subscribe(Topic, Qos)->wait(); + + alertWorkerRunning = true; + + // 开启报警线程 + warnThread(); +} + +// mqtt接收订阅消息的回调:不要起线程,直接更新“最新结果” +void getMsgCallback(mqtt::const_message_ptr msg) +{ + const std::string payload = msg->to_string(); + try + { + auto json = nlohmann::json::parse(payload); + + std::vector dets; + dets.reserve(json.size()); + for (const auto &ii : json) + { + Dection d; + d.x = static_cast(ii.value("x", 0.0)); + d.y = static_cast(ii.value("y", 0.0)); + d.w = static_cast(ii.value("w", 0.0)); + d.h = static_cast(ii.value("h", 0.0)); + d.distance = static_cast(ii.value("distance", 0.0)); + dets.push_back(d); + } + + // 更新绘制用的 latestDection(你原来的逻辑) + { + lock_guard lk(detMutex); + latestDection = dets; // 保留给画框使用;如要极致可改成 move + 双缓冲 + } + + // 更新报警用的 “最新结果”(覆盖旧的,避免队列堆积导致延迟) + { + std::lock_guard lk(latestAlertMutex); + latestAlertDets = std::move(dets); + latestAlertSeq.fetch_add(1, std::memory_order_relaxed); + } + latestAlertCv.notify_one(); + } + catch (const nlohmann::json::parse_error &e) + { + cerr << "JSON 解析错误: " << e.what() << "\n原始 payload: " << payload << "\n"; + } + catch (const std::exception &e) + { + cerr << "处理消息异常: " << e.what() << "\n"; + } +} + +// 摄像头初始化 +bool videoInit(VideoCapture &cap) +{ + // 显式使用 V4L2,避免走 GStreamer + if (cap.isOpened()) + cap.release(); + if (!cap.open("/dev/video10", cv::CAP_V4L2)) + { + cerr << "摄像头打开失败:/dev/video10" << endl; + return false; + } + + cap.set(CAP_PROP_FRAME_WIDTH, 640); + cap.set(CAP_PROP_FRAME_HEIGHT, 480); + cap.set(CAP_PROP_FPS, 30); + cap.set(CAP_PROP_BUFFERSIZE, 1); + // 尝试 MJPG,若不支持则忽略 + cap.set(CAP_PROP_FOURCC, VideoWriter::fourcc('M', 'J', 'P', 'G')); + double fccv = cap.get(CAP_PROP_FOURCC); + char fcc[5] = {(char)((int)fccv & 0xFF), (char)(((int)fccv >> 8) & 0xFF), (char)(((int)fccv >> 16) & 0xFF), (char)(((int)fccv >> 24) & 0xFF), 0}; + cout << "摄像头初始化成功 分辨率=" << cap.get(CAP_PROP_FRAME_WIDTH) + << "x" << cap.get(CAP_PROP_FRAME_HEIGHT) + << " FPS=" << cap.get(CAP_PROP_FPS) + << " FOURCC=" << fcc << endl; + return true; +} + +// FFmpeg管道初始化 +FILE *pipeInit() +{ + FILE *pipe = popen( + "ffmpeg " + "-nostats -hide_banner -loglevel error " + "-f rawvideo -pixel_format bgr24 -video_size 640x480 -framerate 30 -i - " + "-c:v h264_rkmpp -rc_mode 2 -qp_init 32 -profile:v baseline -g 1 -bf 0 " + "-fflags nobuffer -flags low_delay " + "-rtsp_transport tcp -f rtsp rtsp://127.0.0.1:8554/stream", + "w"); + + if (!pipe) + { + cerr << "FFmpeg管道打开失败" << endl; + return nullptr; + } + + // 设置无缓冲模式 + setvbuf(pipe, NULL, _IONBF, 0); + cout << "FFmpeg管道初始化成功" << endl; + + return pipe; +} + +// 处理单帧 +bool processFrame(VideoCapture &cap, FILE *pipe, Mat &frame, int64 &count, chrono::steady_clock::time_point &t0) +{ + // 读取帧(失败不退出,短休眠重试) + if (!cap.read(frame) || frame.empty()) + { + cerr << "读取帧失败,重试中..." << endl; + this_thread::sleep_for(50ms); + return true; + } + handleFrame = frame.clone(); + + vector destCopy; + // 读取最新检测:短锁获取并将结果拷贝到本地变量 + { + lock_guard lk(detMutex); + destCopy = latestDection; // 复制到本地 + latestDection.clear(); + } + + // 在主线程上进行绘制 + for (const auto &ii : destCopy) + { + drawRect(ii.x, ii.y, ii.w, ii.h, ii.distance); + } + + // 每3秒刷新一次区域坐标并绘制 + static auto lastZonesRefresh = std::chrono::steady_clock::now(); + auto now = std::chrono::steady_clock::now(); + if (now - lastZonesRefresh >= std::chrono::seconds(5)) + { + ReloadConfigIfChanged(); + lastZonesRefresh = now; + } + + // 绘制三类区域框并按需镜像/翻转(推流输出处理后画面) + SetMirror(handleFrame); + drawZones(handleFrame); + + // 确保输出给 FFmpeg 的尺寸与像素格式(BGR24, 640x480) + Mat outFrame; + if (handleFrame.cols != 640 || handleFrame.rows != 480) + resize(handleFrame, outFrame, Size(640, 480)); + else + outFrame = handleFrame; + + // 写入管道(推流处理后画面) + fwrite(outFrame.data, 1, outFrame.total() * outFrame.elemSize(), pipe); + fflush(pipe); + + // 短锁进行保存 + { + lock_guard lk(bufferMutex); + videoDeque.push_back(handleFrame.clone()); // 确保存入深拷贝 + if (videoDeque.size() > MAX_BUFFER_SIZE) + videoDeque.pop_front(); + } + + return true; +} + +// 主处理循环 +void mainLoop(VideoCapture &cap, FILE *pipe) +{ + int64 count = 0; + auto t0 = chrono::steady_clock::now(); + Mat frame; + cout << "开始视频处理循环..." << endl; + + // 创建全屏窗口 + namedWindow("处理后的画面", WINDOW_NORMAL); + setWindowProperty("处理后的画面", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN); + + // 获取屏幕尺寸(通过获取全屏窗口的实际大小) + cv::Rect windowRect = getWindowImageRect("处理后的画面"); + Display *display = XOpenDisplay(nullptr); + int screen = DefaultScreen(display); + int width = DisplayWidth(display, screen); + int height = DisplayHeight(display, screen); + + Mat displayFrame; // 用于存储缩放后的画面 + + while (mainRunning) + { + if (!processFrame(cap, pipe, frame, count, t0)) + { + break; + } + + // 将 handleFrame 缩放到全屏尺寸 + resize(handleFrame, displayFrame, Size(width, height)); + imshow("处理后的画面", displayFrame); + // imshow("csv", handleFrame); + + // 检测退出键 + if (cv::waitKey(1) == 'q') + { + cout << "用户请求退出" << endl; + alertWorkerRunning = false; + break; + } + } +} + +// 资源清理 +void cleanup(FILE *pipe, VideoCapture &cap) +{ + // 防御式确保报警线程条件被唤醒 + alertWorkerRunning = false; + latestAlertCv.notify_all(); + alertcv.notify_all(); + + try + { + client.disconnect()->wait(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } + + if (pipe) + { + pclose(pipe); + cout << "FFmpeg管道已关闭" << endl; + } + + if (cap.isOpened()) + { + cap.release(); + cout << "摄像头已释放" << endl; + } + + destroyAllWindows(); + cout << "所有资源已清理完毕" << endl; +} \ No newline at end of file diff --git a/VideoProsessing/src/makefile b/VideoProsessing/src/makefile new file mode 100644 index 0000000..f400f5b --- /dev/null +++ b/VideoProsessing/src/makefile @@ -0,0 +1,14 @@ +all: video + +PKG_CFLAGS := $(shell pkg-config --cflags opencv4) +PKG_LIBS := $(shell pkg-config --libs opencv4) + +Lib=/home/orangepi/RKApp/VideoProsessing/NetraLib/src/Netra.cpp +Dir=/home/orangepi/RKApp/VideoProsessing/NetraLib/include + +video: main.cpp + g++ -g -o video main.cpp $(Lib) -I$(Dir) $(PKG_CFLAGS) $(PKG_LIBS) -lpaho-mqttpp3 -lpaho-mqtt3a -lpthread -lX11 + mv video ../bin/ + +clean: + rm -rf ../bin/video diff --git a/softWareInit/NetraLib/README.md b/softWareInit/NetraLib/README.md new file mode 100644 index 0000000..c680108 --- /dev/null +++ b/softWareInit/NetraLib/README.md @@ -0,0 +1,128 @@ +# NetraLib +c/c++基本开发库 + +# TCP 服务端操作 +包括多线程客户端连接,指定客户端数据的收发等等功能 + +# Linux 中屏蔽所有信号操作 +屏蔽所有信号,以防止意外退出 + + +# 写文件操作 +允许原文本进行覆盖写,追加写 +允许二进制进行覆盖写,追加写 +允许在特定位置后面进行插入覆盖操作 +允许删除特定字段后面所有内容在进行写操作 +可以根据需要计算特定符号最后一个字节或者第一个字节所在位置所在位置 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 读文件操作 +支持全文读取(文本和二进制模式) +支持按行读取文本内容 +支持按指定字节数读取数据 +支持计算第一个指定字节序列结束位置(包含该序列本身)的字节数 +提供文件是否存在和文件大小查询 +支持重置文件读取位置,实现多次读取 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 字符串操作 +支持左右空格删除 +支持格式化输出 + + +# Http请求 +提供基于 `cpp-httplib` 的简易 HTTP 客户端封装 `NetRequest`,支持: +1. 同步/异步 GET、POST(JSON、表单) +2. 连接/读写超时设置、Keep-Alive +3. 并发请求上限控制 +4. 可选内存缓存(GET 命中时不发起网络请求) +5. 简单日志回调与性能统计 +6. 断点续传下载到本地文件 + +使用步骤: + +1) 引入头文件,配置目标地址 +```cpp +#include "NetRequest.hpp" + +ntq::RequestOptions opt; +opt.scheme = "http"; // 或 https +opt.host = "127.0.0.1"; // 服务器地址 +opt.port = 8080; // 端口(https 一般 443) +opt.base_path = "/api"; // 可选统一前缀 +opt.connect_timeout_ms = 3000; +opt.read_timeout_ms = 8000; +opt.write_timeout_ms = 8000; +opt.default_headers = { {"Authorization", "Bearer TOKEN"} }; // 可选 + +ntq::NetRequest req(opt); +req.setMaxConcurrentRequests(4); +req.enableCache(std::chrono::seconds(10)); +``` + +2) 发送 GET 请求 +```cpp +auto r = req.Get("/info"); +if (r && r->status == 200) { + // r->body 为返回内容 +} + +// 带查询参数与额外请求头 +httplib::Params q = {{"q","hello"},{"page","1"}}; +httplib::Headers h = {{"X-Req-Id","123"}}; +auto r2 = req.Get("/search", q, h); +``` + +3) 发送 POST 请求 +```cpp +// JSON(Content-Type: application/json) +std::string json = R"({"name":"orangepi","mode":"demo"})"; +auto p1 = req.PostJson("/set", json); + +// 表单(application/x-www-form-urlencoded) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = req.PostForm("/login", form); +``` + +4) 异步调用 +```cpp +auto fut = req.GetAsync("/info"); +auto res = fut.get(); // 与同步用法一致 +``` + +5) 下载到本地(支持断点续传) +```cpp +bool ok = req.DownloadToFile("/files/pkg.bin", "/tmp/pkg.bin", {}, /*resume*/true); +``` + +6) 统计信息 +```cpp +auto s = req.getStats(); +// s.total_requests / s.total_errors / s.last_latency_ms / s.avg_latency_ms +``` + +说明: +- 若使用 HTTPS,需在编译时添加 `-DCPPHTTPLIB_OPENSSL_SUPPORT` 并链接 `-lssl -lcrypto`,且将 `opt.scheme` 设为 `"https"`、端口通常为 `443`。 +- `base_path` 与各函数传入的 `path` 会自动合并,例如 `base_path="/api"` 且 `Get("/info")` 实际请求路径为 `/api/info`。 + +7) 快速用法(无需实例化) +```cpp +using ntq::NetRequest; + +// 直接传完整 URL 发起 GET +auto g = NetRequest::QuickGet("http://127.0.0.1:8080/api/info"); + +// 直接传完整 URL 发起 POST(JSON) +auto p1 = NetRequest::QuickPostJson( + "http://127.0.0.1:8080/api/set", + R"({"name":"orangepi","mode":"demo"})" +); + +// 直接传完整 URL 发起 POST(表单) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = NetRequest::QuickPostForm("http://127.0.0.1:8080/login", form); +``` diff --git a/softWareInit/NetraLib/include/NetRequest.hpp b/softWareInit/NetraLib/include/NetRequest.hpp new file mode 100644 index 0000000..3cb473c --- /dev/null +++ b/softWareInit/NetraLib/include/NetRequest.hpp @@ -0,0 +1,344 @@ +/* +本文件 +网络请求类需要实现以下功能: +1. 发送网络请求 +2. 接收网络响应 +3. 处理网络请求和响应 +4. 实现网络请求和响应的回调函数 +5. 实现网络请求和响应的错误处理 +6. 实现网络请求和响应的日志记录 +7. 实现网络请求和响应的性能统计 +8. 实现网络请求和响应的并发控制 +9. 实现网络请求和响应的缓存管理 +10. 实现网络请求和响应的断点续传 +11. 实现网络请求和响应的断点续传 +12. 实现网络请求和响应的断点续传 +13. 实现网络请求和响应的断点续传 +14. 实现网络请求和响应的断点续传 +*/ +#pragma once +#include "httplib.h" +#include +#include +#include +#include + +// C++17/14 可选类型回退适配:统一使用 ntq::optional / ntq::nullopt +#if defined(__has_include) + #if __has_include() + #include + // 仅当启用了 C++17 或库声明了 optional 功能时,才使用 std::optional + #if defined(__cpp_lib_optional) || (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif +#else + // 无 __has_include:按语言级别判断 + #if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + #include + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #else + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #endif +#endif + +namespace ntq +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief HTTP 响应对象 + * + * 用于承载一次请求返回的状态码、正文与响应头; + * 当 from_cache 为 true 时,表示该响应来自本地缓存(而非实时网络)。 + */ + struct HttpResponse + { + int status = 0; ///< HTTP 状态码(例如 200, 404 等) + std::string body; ///< 响应正文 + httplib::Headers headers; ///< 响应头(大小写不敏感) + bool from_cache = false; ///< 是否来自缓存 + }; + + /** + * @brief 错误码定义 + */ + enum class ErrorCode + { + None = 0, ///< 无错误 + Network, ///< 网络错误(连接失败/发送失败/接收失败等) + Timeout, ///< 超时 + Canceled, ///< 被取消 + InvalidURL, ///< URL 非法 + IOError, ///< 本地 IO 错误(如写文件失败) + SSL, ///< SSL/HTTPS 相关错误 + Unknown ///< 未分类错误 + }; + + /** + * @brief 请求级别的参数配置 + * + * 包含基础连接信息(协议、主机、端口)与默认头部、超时设置等。 + */ + struct RequestOptions + { + std::string scheme = "http"; ///< 协议:http 或 https + std::string host; ///< 目标主机名或 IP(必填) + int port = 80; ///< 端口,http 默认为 80,https 通常为 443 + std::string base_path; ///< 可选的统一前缀(例如 "/api/v1") + + int connect_timeout_ms = 5000; ///< 连接超时(毫秒) + int read_timeout_ms = 10000; ///< 读取超时(毫秒) + int write_timeout_ms = 10000; ///< 写入超时(毫秒) + bool keep_alive = true; ///< 是否保持连接(Keep-Alive) + + httplib::Headers default_headers; ///< 默认头部,随所有请求发送 + }; + + /** + * @class NetRequest + * @brief HTTP 客户端封装(基于 cpp-httplib) + * + * 提供同步和异步的 GET/POST 请求、并发限制、可选缓存、日志回调、 + * 性能统计以及断点续传下载到文件等能力。 + */ + class NetRequest + { + public: + using LogCallback = std::function; ///< 日志回调类型 + + /** + * @brief 运行时统计数据 + */ + struct Stats + { + uint64_t total_requests = 0; ///< 累计请求次数 + uint64_t total_errors = 0; ///< 累计失败次数 + double last_latency_ms = 0.0;///< 最近一次请求耗时(毫秒) + double avg_latency_ms = 0.0; ///< 指数平滑后的平均耗时(毫秒) + }; + + /** + * @brief 构造函数 + * @param options 请求参数配置(目标地址、超时、默认头部等) + */ + explicit NetRequest(const RequestOptions &options); + /** + * @brief 析构函数 + */ + ~NetRequest(); + + /** + * @brief 设置日志回调 + * @param logger 回调函数,接受一行日志字符串 + */ + void setLogger(LogCallback logger); + /** + * @brief 设置最大并发请求数 + * @param n 并发上限(最小值为 1) + */ + void setMaxConcurrentRequests(size_t n); + /** + * @brief 启用内存缓存 + * @param ttl 缓存有效期(超时后自动失效) + */ + void enableCache(std::chrono::milliseconds ttl); + /** + * @brief 禁用并清空缓存 + */ + void disableCache(); + + /** + * @brief 发送同步 GET 请求 + * @param path 资源路径(会与 base_path 合并) + * @param query 查询参数(会拼接为 ?k=v&...) + * @param headers 额外请求头(与默认头部合并) + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional Get(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST JSON 请求 + * @param path 资源路径 + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST 表单请求(application/x-www-form-urlencoded) + * @param path 资源路径 + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 GET 请求 + * @return std::future,用于获取响应结果 + */ + std::future> GetAsync(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST JSON 请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST 表单请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 下载文件到本地,支持断点续传 + * @param path 资源路径 + * @param local_file 本地保存路径 + * @param headers 额外头部 + * @param resume 是否启用续传(Range) + * @param chunk_size 预留参数:分块大小(当前由 httplib 内部回调驱动) + * @param err 可选错误码输出 + * @return true 下载成功(200 或 206),false 失败 + */ + bool DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers = {}, + bool resume = true, + size_t chunk_size = 1 << 15, + ErrorCode *err = nullptr); + + /** + * @brief 获取统计数据快照 + */ + Stats getStats() const; + + /** + * @brief 便捷:直接用完整 URL 发起 GET(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickGet(const std::string &url, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST JSON(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST 表单(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + private: + struct Impl; + Impl *impl_; + }; +} \ No newline at end of file diff --git a/softWareInit/NetraLib/include/Netra.hpp b/softWareInit/NetraLib/include/Netra.hpp new file mode 100644 index 0000000..362ae28 --- /dev/null +++ b/softWareInit/NetraLib/include/Netra.hpp @@ -0,0 +1,426 @@ +#pragma once +#include "QCL_Include.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @class TcpServer + * @brief 简单的多线程TCP服务器类,支持多个客户端连接,数据收发及断开处理 + * + * 该类使用一个线程专门用于监听客户端连接, + * 每当有客户端连接成功时,为其创建一个独立线程处理该客户端的数据收发。 + * 线程安全地管理所有客户端Socket句柄。 + */ + class TcpServer + { + public: + /** + * @brief 构造函数,指定监听端口 + * @param port 服务器监听端口号 + */ + TcpServer(int port); + + /** + * @brief 析构函数,自动调用 stop() 停止服务器并清理资源 + */ + ~TcpServer(); + + /** + * @brief 启动服务器,创建监听socket,开启监听线程 + * @return 启动成功返回true,失败返回false + */ + bool start(); + + /** + * @brief 停止服务器,关闭所有连接,释放资源,等待所有线程退出 + */ + void stop(); + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端Socket描述符 + * @param message 发送的字符串消息 + */ + void sendToClient(int clientSock, const std::string &message); + + /** + * @brief 从指定客户端接收数据(单次调用) + * @param clientSock 客户端Socket描述符 + * @param flag:false 非阻塞模式,true 阻塞模式 + */ + std::string receiveFromClient(int clientSock, bool flag = true); + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *getClientIPAndPort(int clientSock); + + /** + * @brief 获取当前所有已连接客户端Socket的副本 + * @return 包含所有客户端Socket的vector,线程安全 + */ + std::vector getClientSockets(); + + /** + * @brief 从服务器的客户端列表中移除并关闭一个客户端socket + * @param clientSock 客户端Socket描述符 + */ + void removeClient(int clientSock); + + /** + * @brief 非阻塞探测客户端是否已断开(不消耗数据) + * @param clientSock 客户端Socket描述符 + * @return true 已断开或发生致命错误;false 仍然存活或暂无数据 + */ + bool isClientDisconnected(int clientSock); + + private: + /** + * @brief 监听并接受新的客户端连接(运行在独立线程中) + */ + void acceptClients(); + + private: + int serverSock_; ///< 服务器监听Socket描述符 + int port_; ///< 服务器监听端口 + std::atomic running_; ///< 服务器运行状态标志(线程安全) + std::vector clientThreads_; ///< 用于处理每个客户端的线程集合 + std::thread acceptThread_; ///< 负责监听新连接的线程 + std::mutex clientsMutex_; ///< 保护clientSockets_的互斥锁 + std::vector clientSockets_; ///< 当前所有连接的客户端Socket集合 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief 文件写入工具类(线程安全) + * + * 该类支持多种文件写入方式: + * - 覆盖写文本 + * - 追加写文本 + * - 按位置覆盖原文写入 + * - 二进制覆盖写 + * - 二进制追加写 + * + * 特点: + * - 文件不存在时自动创建 + * - 支持覆盖和追加两种模式 + * - 支持二进制模式,适合写入非文本数据 + * - 内部使用 std::mutex 实现线程安全 + */ + class WriteFile + { + public: + /** + * @brief 构造函数 + * @param filePath 文件路径 + */ + explicit WriteFile(const std::string &filePath); + + /** + * @brief 覆盖写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteText(const std::string &content); + + /** + * @brief 追加写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendText(const std::string &content); + + /** + * @brief 覆盖写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteBinary(const std::vector &data); + + /** + * @brief 追加写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendBinary(const std::vector &data); + + /** + * @brief 计算第一个指定字节序列前的字节数 + * @param pattern 要查找的字节序列 + * @param includePattern true 表示返回值包含 pattern 自身长度,false 表示不包含 + * @return size_t 字节数,如果文件没打开或 pattern 为空则返回 0 + */ + size_t countBytesPattern(const std::string &pattern, bool includePattern = false); + + /** + * @brief 在文件中查找指定字节序列并在其后写入内容,如果不存在则追加到文件末尾 + * @param pattern 要查找的字节序列 + * @param content 要写入的内容 + * @return true 写入成功,false 文件打开失败 + * + * 功能说明: + * 1. 若文件中存在 pattern,则删除 pattern 之后的所有内容,并在其后插入 content。 + * 2. 若文件中不存在 pattern,则在文件末尾追加 content,若末尾无换行符则先补充换行。 + */ + bool writeAfterPatternOrAppend(const std::string &pattern, const std::string &content); + + /** + * @brief 在文件指定位置之后插入内容 + * @param content 要插入的内容 + * @param pos 插入位置(从文件开头算起的字节偏移量) + * @param length 插入的长度(>= content.size() 时,多余部分用空字节填充;< content.size() 时只截取前 length 个字节) + * @return true 插入成功,false 文件打开失败或参数不合法 + * + * 功能说明: + * 1. 不会覆盖原有数据,而是将 pos 之后的内容整体向后移动 length 个字节。 + * 2. 如果 length > content.size(),则在 content 后补充 '\0'(或空格,可按需求改)。 + * 3. 如果 length < content.size(),则只写入 content 的前 length 个字节。 + * 4. 文件整体大小会增加 length 个字节。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * insertAfterPos("XY", 2, 3) // 在索引 2 后插入 + * 结果: "ABX Y\0CDEFG" (这里 \0 代表补充的空字节) + */ + bool insertAfterPos(const std::string &content, size_t pos, size_t length); + + /** + * @brief 在文件指定位置覆盖写入内容 + * @param content 要写入的内容 + * @param pos 覆盖起始位置(从文件开头算起的字节偏移量) + * @param length 覆盖长度 + * @return true 覆盖成功,false 文件打开失败或 pos 越界 + * + * 功能说明: + * 1. 从 pos 开始覆盖 length 个字节,不会移动或增加文件大小。 + * 2. 如果 content.size() >= length,则只写入前 length 个字节。 + * 3. 如果 content.size() < length,则写入 content,并用 '\0' 补齐至 length。 + * 4. 如果 pos + length 超过文件末尾,则只覆盖到文件尾部,不会越界。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * overwriteAtPos("XY", 2, 3) + * 结果: "ABXYEFG" (原 "CDE" 被 "XY\0" 覆盖,\0 实际不可见) + */ + bool overwriteAtPos(const std::string &content, size_t pos, size_t length); + + void close(); + + private: + std::string filePath_; ///< 文件路径 + std::mutex writeMutex_; ///< 线程锁,保证多线程写入安全 + + /** + * @brief 通用文本写入接口(线程安全) + * @param content 要写入的内容 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeToFile(const std::string &content, std::ios::openmode mode); + + /** + * @brief 通用二进制写入接口(线程安全) + * @param data 要写入的二进制数据 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeBinary(const std::vector &data, std::ios::openmode mode); + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief ReadFile 类 - 读文件操作工具类 + * + * 功能: + * 1. 读取全文(文本 / 二进制) + * 2. 按行读取 + * 3. 按字节数读取 + * 4. 获取指定字节序列前的字节数(包含该字节序列) + * 5. 检查文件是否存在 + * 6. 获取文件大小 + * + * 设计: + * - 与 WriteFile 类风格保持一致 + * - 支持文本文件与二进制文件 + * - 自动关闭文件(析构时) + * - 内部使用 std::mutex 实现线程安全 + */ + class ReadFile + { + public: + /** + * @brief 构造函数 + * @param filename 文件路径 + */ + explicit ReadFile(const std::string &filename); + + /** + * @brief 析构函数,自动关闭文件 + */ + ~ReadFile(); + + /** + * @brief 打开文件(以二进制方式) + * @return true 打开成功 + * @return false 打开失败 + */ + bool Open(); + + /** + * @brief 关闭文件 + */ + void Close(); + + /** + * @brief 文件是否已经打开 + */ + bool IsOpen() const; + + /** + * @brief 读取全文(文本模式) + * @return 文件内容字符串 + */ + std::string ReadAllText(); + + /** + * @brief 读取全文(二进制模式) + * @return 文件内容字节数组 + */ + std::vector ReadAllBinary(); + + /** + * @brief 按行读取文本 + * @return 每行作为一个字符串的 vector + */ + std::vector ReadLines(); + + /** + * @brief 读取指定字节数 + * @param count 要读取的字节数 + * @return 实际读取到的字节数据 + */ + std::vector ReadBytes(size_t count); + + /** + * @brief 获取指定字节序列前的字节数(包含该字节序列) + * @param marker 要查找的字节序列(可能不止一个字节) + * @return 如果找到,返回前面部分字节数;找不到返回0 + */ + size_t GetBytesBefore(const std::string &marker, bool includeMarker = false); + + /** + * @brief 从指定位置读取指定字节数,默认读取到文件末尾 + * @param pos 起始位置(字节偏移) + * @param count 要读取的字节数,默认为0表示读取到文件末尾 + * @return 读取到的字节数据 + */ + std::vector ReadBytesFrom(size_t pos, size_t count = 0); + + /** + * @brief 检查文件是否存在 + */ + bool FileExists() const; + + /** + * @brief 获取文件大小(字节数) + */ + size_t GetFileSize() const; + + /** + * @brief 重置读取位置到文件开头 + */ + void Reset(); + + private: + std::string filename_; // 文件路径 + std::ifstream file_; // 文件流对象 + mutable std::mutex mtx_; // 可变,保证 const 方法也能加锁 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals(); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 字符串操作 + // 去除字符串的左空格 + std::string Ltrim(const std::string &s); + + // 去除字符串右侧的空格 + std::string Rtrim(const std::string &s); + + // 去除字符串左右两侧的空格 + std::string LRtrim(const std::string &s); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // c++进行格式化输出 + // 通用类型转字符串 + template + std::string to_string_any(const T &value) + { + std::ostringstream oss; + oss << value; + return oss.str(); + } + + // 递归获取 tuple 中 index 对应参数 + template + std::string get_tuple_arg(const Tuple &tup, std::size_t index) + { + if constexpr (I < std::tuple_size_v) + { + if (I == index) + return to_string_any(std::get(tup)); + else + return get_tuple_arg(tup, index); + } + else + { + throw std::runtime_error("Too few arguments for format string"); + } + } + + // format 函数 + template + std::string format(const std::string &fmt, const Args &...args) + { + std::ostringstream oss; + std::tuple tup(args...); + size_t pos = 0; + size_t arg_idx = 0; + + while (pos < fmt.size()) + { + if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '{') + { + oss << '{'; + pos += 2; + } + else if (fmt[pos] == '}' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << '}'; + pos += 2; + } + else if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << get_tuple_arg(tup, arg_idx++); + pos += 2; + } + else + { + oss << fmt[pos++]; + } + } + + if (arg_idx < sizeof...(Args)) + throw std::runtime_error("Too many arguments for format string"); + + return oss.str(); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +} diff --git a/softWareInit/NetraLib/include/QCL_Include.hpp b/softWareInit/NetraLib/include/QCL_Include.hpp new file mode 100644 index 0000000..52a20fb --- /dev/null +++ b/softWareInit/NetraLib/include/QCL_Include.hpp @@ -0,0 +1,55 @@ +/** + * @file qcl_include.hpp + * @brief 通用C++开发头文件集合(Linux环境) + * @note 使用前确保目标平台支持C++17标准 + */ +#ifndef QCL_INCLUDE_HPP +#define QCL_INCLUDE_HPP + +// ==================== C/C++基础运行时库 ==================== +#include // 标准输入输出流(cin/cout/cerr) +#include // std::string类及相关操作 +#include // C风格字符串操作(strcpy/strcmp等) +#include // 通用工具函数(atoi/rand/malloc等) +#include // C风格IO(printf/scanf) +#include // 断言宏(调试期检查) +#include // 数学函数(sin/pow等) +#include // 时间处理(time/clock) +#include // 信号处理(signal/kill) +#include // 智能指针 + +// ==================== STL容器与算法 ==================== +#include // 动态数组(连续内存容器) +#include // 双向链表 +#include // 双端队列 +#include // 有序键值对(红黑树实现) +#include // 有序集合 +#include // 哈希表实现的键值对 +#include // 哈希表实现的集合 +#include // 栈适配器(LIFO) +#include // 队列适配器(FIFO) +#include // 通用算法(sort/find等) +#include // 数值算法(accumulate等) +#include // 迭代器相关 + +// ==================== 字符串与流处理 ==================== +#include // 字符串流(内存IO) +#include // 文件流(文件IO) +#include // 流格式控制(setw/setprecision) +#include // 正则表达式 +#include // 文件系统(C++17) +#include + +// ==================== 并发编程支持 ==================== +#include // 线程管理(std::thread) +#include // 互斥锁(mutex/lock_guard) +#include // 原子操作(线程安全变量) +#include // 条件变量(线程同步) + +// ==================== Linux网络编程 ==================== +#include // 套接字基础API(socket/bind) +#include // IPV4/IPV6地址结构体 +#include // 地址转换函数(inet_pton等) +#include // POSIX API(close/read/write) + +#endif // QCL_INCLUDE_HPP diff --git a/softWareInit/NetraLib/include/README.md b/softWareInit/NetraLib/include/README.md new file mode 100644 index 0000000..984ab89 --- /dev/null +++ b/softWareInit/NetraLib/include/README.md @@ -0,0 +1,850 @@ +cpp-httplib +=========== + +[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions) + +A C++11 single-file header-only cross platform HTTP/HTTPS library. + +It's extremely easy to setup. Just include the **httplib.h** file in your code! + +NOTE: This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. + +Simple examples +--------------- + +#### Server (Multi-threaded) + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Server svr; + +// HTTPS +httplib::SSLServer svr; + +svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { + res.set_content("Hello World!", "text/plain"); +}); + +svr.listen("0.0.0.0", 8080); +``` + +#### Client + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co"); + +// HTTPS +httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co"); + +auto res = cli.Get("/hi"); +res->status; +res->body; +``` + +SSL Support +----------- + +SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. + +NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0. + +NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`. + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// Server +httplib::SSLServer svr("./cert.pem", "./key.pem"); + +// Client +httplib::Client cli("https://localhost:1234"); // scheme + host +httplib::SSLClient cli("localhost:1234"); // host +httplib::SSLClient cli("localhost", 1234); // host, port + +// Use your CA bundle +cli.set_ca_cert_path("./ca-bundle.crt"); + +// Disable cert verification +cli.enable_server_certificate_verification(false); +``` + +NOTE: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. + +Server +------ + +```c++ +#include + +int main(void) +{ + using namespace httplib; + + Server svr; + + svr.Get("/hi", [](const Request& req, Response& res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { + auto numbers = req.matches[1]; + res.set_content(numbers, "text/plain"); + }); + + svr.Get("/body-header-param", [](const Request& req, Response& res) { + if (req.has_header("Content-Length")) { + auto val = req.get_header_value("Content-Length"); + } + if (req.has_param("key")) { + auto val = req.get_param_value("key"); + } + res.set_content(req.body, "text/plain"); + }); + + svr.Get("/stop", [&](const Request& req, Response& res) { + svr.stop(); + }); + + svr.listen("localhost", 1234); +} +``` + +`Post`, `Put`, `Delete` and `Options` methods are also supported. + +### Bind a socket to multiple interfaces and any available port + +```cpp +int port = svr.bind_to_any_port("0.0.0.0"); +svr.listen_after_bind(); +``` + +### Static File Server + +```cpp +// Mount / to ./www directory +auto ret = svr.set_mount_point("/", "./www"); +if (!ret) { + // The specified base directory doesn't exist... +} + +// Mount /public to ./www directory +ret = svr.set_mount_point("/public", "./www"); + +// Mount /public to ./www1 and ./www2 directories +ret = svr.set_mount_point("/public", "./www1"); // 1st order to search +ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search + +// Remove mount / +ret = svr.remove_mount_point("/"); + +// Remove mount /public +ret = svr.remove_mount_point("/public"); +``` + +```cpp +// User defined file extension and MIME type mappings +svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); +``` + +The followings are built-in mappings: + +| Extension | MIME Type | Extension | MIME Type | +| :--------- | :-------------------------- | :--------- | :-------------------------- | +| css | text/css | mpga | audio/mpeg | +| csv | text/csv | weba | audio/webm | +| txt | text/plain | wav | audio/wave | +| vtt | text/vtt | otf | font/otf | +| html, htm | text/html | ttf | font/ttf | +| apng | image/apng | woff | font/woff | +| avif | image/avif | woff2 | font/woff2 | +| bmp | image/bmp | 7z | application/x-7z-compressed | +| gif | image/gif | atom | application/atom+xml | +| png | image/png | pdf | application/pdf | +| svg | image/svg+xml | mjs, js | application/javascript | +| webp | image/webp | json | application/json | +| ico | image/x-icon | rss | application/rss+xml | +| tif | image/tiff | tar | application/x-tar | +| tiff | image/tiff | xhtml, xht | application/xhtml+xml | +| jpeg, jpg | image/jpeg | xslt | application/xslt+xml | +| mp4 | video/mp4 | xml | application/xml | +| mpeg | video/mpeg | gz | application/gzip | +| webm | video/webm | zip | application/zip | +| mp3 | audio/mp3 | wasm | application/wasm | + +### File request handler + +```cpp +// The handler is called right before the response is sent to a client +svr.set_file_request_handler([](const Request &req, Response &res) { + ... +}); +``` + +NOTE: These static file server methods are not thread-safe. + +### Logging + +```cpp +svr.set_logger([](const auto& req, const auto& res) { + your_logger(req, res); +}); +``` + +### Error handler + +```cpp +svr.set_error_handler([](const auto& req, auto& res) { + auto fmt = "

Error Status: %d

"; + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), fmt, res.status); + res.set_content(buf, "text/html"); +}); +``` + +### Exception handler +The exception handler gets called if a user routing handler throws an error. + +```cpp +svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { + auto fmt = "

Error 500

%s

"; + char buf[BUFSIZ]; + try { + std::rethrow_exception(ep); + } catch (std::exception &e) { + snprintf(buf, sizeof(buf), fmt, e.what()); + } catch (...) { // See the following NOTE + snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); + } + res.set_content(buf, "text/html"); + res.status = 500; +}); +``` + +NOTE: if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful! + +### Pre routing handler + +```cpp +svr.set_pre_routing_handler([](const auto& req, auto& res) { + if (req.path == "/hello") { + res.set_content("world", "text/html"); + return Server::HandlerResponse::Handled; + } + return Server::HandlerResponse::Unhandled; +}); +``` + +### Post routing handler + +```cpp +svr.set_post_routing_handler([](const auto& req, auto& res) { + res.set_header("ADDITIONAL_HEADER", "value"); +}); +``` + +### 'multipart/form-data' POST data + +```cpp +svr.Post("/multipart", [&](const auto& req, auto& res) { + auto size = req.files.size(); + auto ret = req.has_file("name1"); + const auto& file = req.get_file_value("name1"); + // file.filename; + // file.content_type; + // file.content; +}); +``` + +### Receive content with a content receiver + +```cpp +svr.Post("/content_receiver", + [&](const Request &req, Response &res, const ContentReader &content_reader) { + if (req.is_multipart_form_data()) { + // NOTE: `content_reader` is blocking until every form data field is read + MultipartFormDataItems files; + content_reader( + [&](const MultipartFormData &file) { + files.push_back(file); + return true; + }, + [&](const char *data, size_t data_length) { + files.back().content.append(data, data_length); + return true; + }); + } else { + std::string body; + content_reader([&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); + } + }); +``` + +### Send content with the content provider + +```cpp +const size_t DATA_CHUNK_SIZE = 4; + +svr.Get("/stream", [&](const Request &req, Response &res) { + auto data = new std::string("abcdefg"); + + res.set_content_provider( + data->size(), // Content length + "text/plain", // Content type + [data](size_t offset, size_t length, DataSink &sink) { + const auto &d = *data; + sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE)); + return true; // return 'false' if you want to cancel the process. + }, + [data](bool success) { delete data; }); +}); +``` + +Without content length: + +```cpp +svr.Get("/stream", [&](const Request &req, Response &res) { + res.set_content_provider( + "text/plain", // Content type + [&](size_t offset, DataSink &sink) { + if (/* there is still data */) { + std::vector data; + // prepare data... + sink.write(data.data(), data.size()); + } else { + sink.done(); // No more data + } + return true; // return 'false' if you want to cancel the process. + }); +}); +``` + +### Chunked transfer encoding + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done(); // No more data + return true; // return 'false' if you want to cancel the process. + } + ); +}); +``` + +With trailer: + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_header("Trailer", "Dummy1, Dummy2"); + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done_with_trailer({ + {"Dummy1", "DummyVal1"}, + {"Dummy2", "DummyVal2"} + }); + return true; + } + ); +}); +``` + +### 'Expect: 100-continue' handler + +By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header. + +```cpp +// Send a '417 Expectation Failed' response. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return 417; +}); +``` + +```cpp +// Send a final status without reading the message body. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return res.status = 401; +}); +``` + +### Keep-Alive connection + +```cpp +svr.set_keep_alive_max_count(2); // Default is 5 +svr.set_keep_alive_timeout(10); // Default is 5 +``` + +### Timeout + +```c++ +svr.set_read_timeout(5, 0); // 5 seconds +svr.set_write_timeout(5, 0); // 5 seconds +svr.set_idle_interval(0, 100000); // 100 milliseconds +``` + +### Set maximum payload length for reading a request body + +```c++ +svr.set_payload_max_length(1024 * 1024 * 512); // 512MB +``` + +### Server-Sent Events + +Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc). + +### Default thread pool support + +`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. + +If you want to set the thread count at runtime, there is no convenient way... But here is how. + +```cpp +svr.new_task_queue = [] { return new ThreadPool(12); }; +``` + +### Override the default thread pool with yours + +You can supply your own thread pool implementation according to your need. + +```cpp +class YourThreadPoolTaskQueue : public TaskQueue { +public: + YourThreadPoolTaskQueue(size_t n) { + pool_.start_with_thread_count(n); + } + + virtual void enqueue(std::function fn) override { + pool_.enqueue(fn); + } + + virtual void shutdown() override { + pool_.shutdown_gracefully(); + } + +private: + YourThreadPool pool_; +}; + +svr.new_task_queue = [] { + return new YourThreadPoolTaskQueue(12); +}; +``` + +Client +------ + +```c++ +#include +#include + +int main(void) +{ + httplib::Client cli("localhost", 1234); + + if (auto res = cli.Get("/hi")) { + if (res->status == 200) { + std::cout << res->body << std::endl; + } + } else { + auto err = res.error(); + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } +} +``` + +NOTE: Constructor with scheme-host-port string is now supported! + +```c++ +httplib::Client cli("localhost"); +httplib::Client cli("localhost:8080"); +httplib::Client cli("http://localhost"); +httplib::Client cli("http://localhost:8080"); +httplib::Client cli("https://localhost"); +httplib::SSLClient cli("localhost"); +``` + +### Error code + +Here is the list of errors from `Result::error()`. + +```c++ +enum Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, +}; +``` + +### GET with HTTP headers + +```c++ +httplib::Headers headers = { + { "Accept-Encoding", "gzip, deflate" } +}; +auto res = cli.Get("/hi", headers); +``` +or +```c++ +auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}}); +``` +or +```c++ +cli.set_default_headers({ + { "Accept-Encoding", "gzip, deflate" } +}); +auto res = cli.Get("/hi"); +``` + +### POST + +```c++ +res = cli.Post("/post", "text", "text/plain"); +res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded"); +``` + +### POST with parameters + +```c++ +httplib::Params params; +params.emplace("name", "john"); +params.emplace("note", "coder"); + +auto res = cli.Post("/post", params); +``` + or + +```c++ +httplib::Params params{ + { "name", "john" }, + { "note", "coder" } +}; + +auto res = cli.Post("/post", params); +``` + +### POST with Multipart Form Data + +```c++ +httplib::MultipartFormDataItems items = { + { "text1", "text default", "", "" }, + { "text2", "aωb", "", "" }, + { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, + { "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" }, + { "file3", "", "", "application/octet-stream" }, +}; + +auto res = cli.Post("/multipart", items); +``` + +### PUT + +```c++ +res = cli.Put("/resource/foo", "text", "text/plain"); +``` + +### DELETE + +```c++ +res = cli.Delete("/resource/foo"); +``` + +### OPTIONS + +```c++ +res = cli.Options("*"); +res = cli.Options("/resource/foo"); +``` + +### Timeout + +```c++ +cli.set_connection_timeout(0, 300000); // 300 milliseconds +cli.set_read_timeout(5, 0); // 5 seconds +cli.set_write_timeout(5, 0); // 5 seconds +``` + +### Receive content with a content receiver + +```c++ +std::string body; + +auto res = cli.Get("/large-data", + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); +``` + +```cpp +std::string body; + +auto res = cli.Get( + "/stream", Headers(), + [&](const Response &response) { + EXPECT_EQ(200, response.status); + return true; // return 'false' if you want to cancel the request. + }, + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; // return 'false' if you want to cancel the request. + }); +``` + +### Send content with a content provider + +```cpp +std::string body = ...; + +auto res = cli.Post( + "/stream", body.size(), + [](size_t offset, size_t length, DataSink &sink) { + sink.write(body.data() + offset, length); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### Chunked transfer encoding + +```cpp +auto res = cli.Post( + "/stream", + [](size_t offset, DataSink &sink) { + sink.os << "chunked data 1"; + sink.os << "chunked data 2"; + sink.os << "chunked data 3"; + sink.done(); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### With Progress Callback + +```cpp +httplib::Client client(url, port); + +// prints: 0 / 000 bytes => 50% complete +auto res = cli.Get("/", [](uint64_t len, uint64_t total) { + printf("%lld / %lld bytes => %d%% complete\n", + len, total, + (int)(len*100/total)); + return true; // return 'false' if you want to cancel the request. +} +); +``` + +![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) + +### Authentication + +```cpp +// Basic Authentication +cli.set_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_bearer_token_auth("token"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Proxy server support + +```cpp +cli.set_proxy("host", port); + +// Basic Authentication +cli.set_proxy_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_proxy_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_proxy_bearer_token_auth("pass"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Range + +```cpp +httplib::Client cli("httpbin.org"); + +auto res = cli.Get("/range/32", { + httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10' +}); +// res->status should be 206. +// res->body should be "bcdefghijk". +``` + +```cpp +httplib::make_range_header({{1, 10}, {20, -1}}) // 'Range: bytes=1-10, 20-' +httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599' +httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1' +``` + +### Keep-Alive connection + +```cpp +httplib::Client cli("localhost", 1234); + +cli.Get("/hello"); // with "Connection: close" + +cli.set_keep_alive(true); +cli.Get("/world"); + +cli.set_keep_alive(false); +cli.Get("/last-request"); // with "Connection: close" +``` + +### Redirect + +```cpp +httplib::Client cli("yahoo.com"); + +auto res = cli.Get("/"); +res->status; // 301 + +cli.set_follow_location(true); +res = cli.Get("/"); +res->status; // 200 +``` + +### Use a specific network interface + +NOTE: This feature is not available on Windows, yet. + +```cpp +cli.set_interface("eth0"); // Interface name, IP address or host name +``` + +Compression +----------- + +The server can apply compression to the following MIME type contents: + + * all text types except text/event-stream + * image/svg+xml + * application/javascript + * application/json + * application/xml + * application/xhtml+xml + +### Zlib Support + +'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked. + +### Brotli Support + +Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/google/brotli for more detail. + +### Compress request body on client + +```c++ +cli.set_compress(true); +res = cli.Post("/resource/foo", "...", "text/plain"); +``` + +### Compress response body on client + +```c++ +cli.set_decompress(false); +res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); +res->body; // Compressed data +``` + +Use `poll` instead of `select` +------------------------------ + +`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`. + + +Split httplib.h into .h and .cc +------------------------------- + +```console +$ ./split.py -h +usage: split.py [-h] [-e EXTENSION] [-o OUT] + +This script splits httplib.h into .h and .cc parts. + +optional arguments: + -h, --help show this help message and exit + -e EXTENSION, --extension EXTENSION + extension of the implementation file (default: cc) + -o OUT, --out OUT where to write the files (default: out) + +$ ./split.py +Wrote out/httplib.h and out/httplib.cc +``` + +NOTE +---- + +### g++ + +g++ 4.8 and below cannot build this library since `` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). + +### Windows + +Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand. + +```cpp +#include +#include +``` + +```cpp +#define WIN32_LEAN_AND_MEAN +#include +#include +``` + +NOTE: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. + +NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported. + +License +------- + +MIT license (© 2023 Yuji Hirose) + +Special Thanks To +----------------- + +[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy! diff --git a/softWareInit/NetraLib/include/encrypt.hpp b/softWareInit/NetraLib/include/encrypt.hpp new file mode 100644 index 0000000..42ea793 --- /dev/null +++ b/softWareInit/NetraLib/include/encrypt.hpp @@ -0,0 +1,14 @@ +#pragma once + +/* +主要是用于各种加密 +*/ + +#include "QCL_Include.hpp" + +using namespace std; + +namespace encrypt +{ + string MD5(const string &info); +} \ No newline at end of file diff --git a/softWareInit/NetraLib/include/httplib.h b/softWareInit/NetraLib/include/httplib.h new file mode 100644 index 0000000..1bdef69 --- /dev/null +++ b/softWareInit/NetraLib/include/httplib.h @@ -0,0 +1,8794 @@ +// +// httplib.h +// +// Copyright (c) 2023 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.12.2" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif // strcasecmp + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#ifndef _AIX +#include +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x1010100fL +#error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +struct ci { + bool operator()(const std::string &s1, const std::string &s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); + } +}; + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +} // namespace detail + +using Headers = std::multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + Params params; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + void set_redirect(const std::string &url, int status = 302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + template + ssize_t write_format(const char *fmt, const Args &...args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual void enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool : public TaskQueue { +public: + explicit ThreadPool(size_t n) : shutdown_(false) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + void enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = std::move(pool_.jobs_.front()); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_file_request_handler(Handler handler); + + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = std::vector>; + using HandlersForContentReader = + std::vector>; + + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, const Handlers &handlers); + bool + dispatch_request_for_content_reader(Request &req, Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers); + + bool parse_request_line(const char *s, Request &req); + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); + bool write_response(Stream &strm, bool close_connection, const Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + + virtual bool process_and_close_socket(socket_t sock); + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + + std::atomic is_running_{false}; + std::atomic done_{false}; + std::map file_extension_and_mimetype_map_; + Handler file_request_handler_; + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Logger logger_; + Expect100ContinueHandler expect_100_continue_handler_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(const Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; + template + T get_request_header_value(const std::string &key, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_; + Headers request_headers_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); + + void copy_settings(const ClientImpl &rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif + + Logger logger_; + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + Client(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +template +inline T get_header_value(const Headers & /*headers*/, + const std::string & /*key*/, size_t /*id*/ = 0, + uint64_t /*def*/ = 0) {} + +template <> +inline uint64_t get_header_value(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +template +inline T Request::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline T Response::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf{}; + + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +template +inline T Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, 0); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(Ranges ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void split(const char *b, const char *e, char d, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + virtual ~nocompressor() = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor(); + ~gzip_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + int v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + const char *charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = (code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + int val = 0; + int valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_file(const std::string &path) { +#ifdef _WIN32 + return _access_s(path.c_str(), 0) == 0; +#else + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +#endif +} + +inline bool is_dir(const std::string &path) { + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + int val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + int val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + size_t i = 0; + size_t beg = 0; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { break; } + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } +} + +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = false; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024 * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + +inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; + } + } +} + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET; + + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + std::copy(host.begin(), host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + + fcntl(sock, F_SETFD, FD_CLOEXEC); + if (socket_options) { socket_options(sock); } + + if (!bind_or_connect(sock, hints)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + if (sock == INVALID_SOCKET) { continue; } + +#ifndef _WIN32 + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + int yes = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), + sizeof(yes)); + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + int no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), + sizeof(no)); + } + + // bind or connect + if (bind_or_connect(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + freeaddrinfo(result); + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + freeifaddrs(ifap); + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + freeifaddrs(ifap); + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + freeifaddrs(ifap); + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if.c_str())) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { return false; } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline const char * +find_content_type(const std::string &path, + const std::map &user_data) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second.c_str(); } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return nullptr; + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline const char *status_message(int status) { + switch (status) { + case 100: return "Continue"; + case 101: return "Switching Protocol"; + case 102: return "Processing"; + case 103: return "Early Hints"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choice"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "unused"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Too Early"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + + default: + case 500: return "Internal Server Error"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + default: + return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + int ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + int ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + auto prev_avail_in = strm_.avail_in; + + ret = inflate(&strm_, Z_NO_FLUSH); + + if (prev_avail_in - strm_.avail_in == 0) { return false; } + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) return false; + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +inline bool compare_case_ignore(const std::string &a, const std::string &b) { + if (a.size() != b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + auto key = std::string(beg, key_end); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + fn(std::move(key), std::move(val)); + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + } else { + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; + } +#else + } else { + continue; // Skip invalid line. + } +#endif + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n < 0) { + return false; + } else if (n == 0) { + return true; + } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n")) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n")) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), + "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = 500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value(x.headers, "Content-Length"); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { status = exceed_payload_max_length ? 413 : 400; } + return ret; + }); +} // namespace detail + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + res.location = location; + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = content_type.substr(beg, end - beg); + if (boundary.length() >= 2 && boundary.front() == '"' && + boundary.back() == '"') { + boundary = boundary.substr(1, boundary.size() - 2); + } + return !boundary.empty(); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); + std::smatch m; + if (std::regex_match(s, m, re_first_range)) { + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); + bool all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) return; + static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); + std::cmatch cm; + if (std::regex_match(b, e, cm, re_another_range)) { + ssize_t first = -1; + if (!cm.str(1).empty()) { + first = static_cast(std::stoll(cm.str(1))); + } + + ssize_t last = -1; + if (!cm.str(2).empty()) { + last = static_cast(std::stoll(cm.str(2))); + } + + if (first != -1 && last != -1 && first > last) { + all_valid_ranges = false; + return; + } + ranges.emplace_back(std::make_pair(first, last)); + } + }); + return all_valid_ranges; + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + // TODO: support 'filename*' + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~", + std::regex_constants::icase); + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + static const std::string header_name = "content-type:"; + const auto header = buf_head(pos); + if (start_with_case_ignore(header, header_name)) { + file_.content_type = trim_copy(header.substr(header_name.size())); + } else { + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + file_.name = m[1]; + file_.filename = m[2]; + } else { + is_valid_ = false; + return false; + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_crlf_.size() > buf_size()) { return true; } + if (buf_start_with(dash_crlf_)) { + buf_erase(dash_crlf_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + const std::string dash_crlf_ = "--\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string to_lower(const char *beg, const char *end) { + std::string out; + auto it = beg; + while (it != end) { + out += static_cast(::tolower(*it)); + it++; + } + return out; +} + +inline std::string make_multipart_data_boundary() { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); + + std::string result = "--cpp-httplib-multipart-data-"; + + for (auto i = 0; i < 16; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + + return result; +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) body += serialize_multipart_formdata_finish(boundary); + + return body; +} + +inline std::pair +get_range_offset_and_length(const Request &req, size_t content_length, + size_t index) { + auto r = req.ranges[index]; + + if (r.first == -1 && r.second == -1) { + return std::make_pair(0, content_length); + } + + auto slen = static_cast(content_length); + + if (r.first == -1) { + r.first = (std::max)(static_cast(0), slen - r.second); + r.second = slen - 1; + } + + if (r.second == -1) { r.second = slen - 1; } + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field(size_t offset, size_t length, + size_t content_length) { + std::string field = "bytes "; + field += std::to_string(offset); + field += "-"; + field += std::to_string(offset + length - 1); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + SToken stoken, CToken ctoken, + Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offsets = get_range_offset_and_length(req, res.body.size(), i); + auto offset = offsets.first; + auto length = offsets.second; + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset, length, res.body.size())); + ctoken("\r\n"); + ctoken("\r\n"); + if (!content(offset, length)) { return false; } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--\r\n"); + + return true; +} + +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; + }); +} + +inline size_t +get_multipart_ranges_data_length(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool write_multipart_ranges_data(Stream &strm, const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + const T &is_shutting_down) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline std::pair +get_range_offset_and_length(const Request &req, const Response &res, + size_t index) { + auto r = req.ranges[index]; + + if (r.second == -1) { + r.second = static_cast(res.content_length_) - 1; + } + + return std::make_pair(r.first, r.second - r.first + 1); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << (unsigned int)hash[i]; + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (int i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + auto m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 +inline std::string random_string(size_t length) { + auto randchar = []() -> char { + const char charset[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[static_cast(std::rand()) % max_index]; + }; + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + int dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } + + freeaddrinfo(result); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair make_range_header(Ranges ranges) { + std::string field = "bytes="; + auto i = 0; + for (auto r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const std::string &key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = 302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = true; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() {} + +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() {} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + if (detail::is_dir(dir)) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(HandlerWithResponse handler) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + if (bind_internal(host, port, socket_flags) < 0) return false; + return true; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + return bind_internal(host, 0, socket_flags); +} + +inline bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + auto se = detail::scope_exit([&]() { done_ = true; }); + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } +} + +inline bool Server::parse_request_line(const char *s, Request &req) { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + size_t count = 0; + + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), req.params); + } + break; + } + default: break; + } + count++; + }); + + if (count > 2) { return false; } + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); + } + + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + res.set_header("Content-Type", "text/plain"); + } + + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { + return false; + } + + if (!detail::write_headers(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + res.content_provider_success_ = false; + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = 413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); + if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } + res.status = req.has_header("Range") ? 206 : 200; + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + task_queue->enqueue([this, sock]() { process_and_close_socket(sock); }); + } + + task_queue->shutdown(); + } + + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + bool is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = 400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { + if (req.ranges.size() > 1) { + boundary = detail::make_multipart_data_boundary(); + + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + res.headers.emplace("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty()) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length(req, res, boundary, + content_type); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty()) { + ; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.body.size(), 0); + auto offset = offsets.first; + auto length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.body.size()); + res.set_header("Content-Range", content_range); + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } + } else { + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + Response res; + + res.version = "HTTP/1.1"; + + for (const auto &header : default_headers_) { + if (res.headers.find(header.first) == res.headers.end()) { + res.headers.insert(header); + } + } + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = 400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = 416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + auto status = 100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case 100: + case 417: + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + detail::status_message(status)); + break; + default: return write_response(strm, close_connection, req, res); + } + } + + // Rounting + bool routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + + if (routed) { + if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(host), port_(port), + host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) ip = it->second; + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == 100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == 407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.headers.emplace("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.headers.emplace("User-Agent", agent); + } +#endif + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.headers.emplace("Content-Length", length); + } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.headers.emplace("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.headers.emplace("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.headers.emplace("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + detail::write_headers(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.headers.emplace("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + ; + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by + // handle_request which is only called by send, which locks the request + // mutex during the process. It would be a bug to call it from a different + // thread since it's a thread-safety issue to do these things to the socket + // if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + size_t cur_item = 0, cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && items.size()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + bool has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + return false; + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, response_handler, + content_receiver, progress); +} + +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + int res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() {} + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + // add default password callback before opening encrypted private key + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata(ctx_, + (char *)private_key_password); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response res2; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, res2, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (res2.status == 407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res2, auth, true)) { + Response res3; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, res3, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } else { + res = res2; + return false; + } + } + + return true; +} + +inline bool SSLClient::load_certs() { + bool ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl2) { + SSL_set_tlsext_host_name(ssl2, host_.c_str()); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6; + struct in_addr addr; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); + auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(std::string(b, e)); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() {} + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); +} + +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline socket_t Client::socket() const { return cli_->socket(); } + +inline void Client::stop() { cli_->stop(); } + +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + +inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/softWareInit/NetraLib/src/NetRequest.cpp b/softWareInit/NetraLib/src/NetRequest.cpp new file mode 100644 index 0000000..f3aa541 --- /dev/null +++ b/softWareInit/NetraLib/src/NetRequest.cpp @@ -0,0 +1,635 @@ +#include "NetRequest.hpp" + +#include +#include +#include +#include +#include +#include + +namespace ntq +{ + namespace + { + static std::string joinPath(const std::string &base, const std::string &path) + { + if (base.empty()) return path.empty() || path[0] == '/' ? path : std::string("/") + path; + if (path.empty()) return base[0] == '/' ? base : std::string("/") + base; + bool base_has = base.front() == '/'; + bool base_end = base.back() == '/'; + bool path_has = path.front() == '/'; + std::string b = base_has ? base : std::string("/") + base; + if (base_end && path_has) return b + path.substr(1); + if (!base_end && !path_has) return b + "/" + path; + return b + path; + } + + static std::string paramsToQuery(const httplib::Params ¶ms) + { + if (params.empty()) return {}; + std::string s; + bool first = true; + for (auto &kv : params) + { + if (!first) s += '&'; + first = false; + s += kv.first; + s += '='; + s += kv.second; + } + return s; + } + + static httplib::Headers mergeHeaders(const httplib::Headers &a, const httplib::Headers &b) + { + httplib::Headers h = a; + for (auto &kv : b) + { + // 覆盖同名 header:先删再插 + h.erase(kv.first); + h.emplace(kv.first, kv.second); + } + return h; + } + } + + class ConcurrencyGate + { + public: + explicit ConcurrencyGate(size_t limit) : limit_(limit), active_(0) {} + + void set_limit(size_t limit) + { + std::lock_guard lk(mtx_); + limit_ = limit > 0 ? limit : 1; + cv_.notify_all(); + } + + struct Guard + { + ConcurrencyGate &g; + explicit Guard(ConcurrencyGate &gate) : g(gate) { g.enter(); } + ~Guard() { g.leave(); } + }; + + private: + friend struct Guard; + void enter() + { + std::unique_lock lk(mtx_); + cv_.wait(lk, [&]{ return active_ < limit_; }); + ++active_; + } + void leave() + { + std::lock_guard lk(mtx_); + if (active_ > 0) --active_; + cv_.notify_one(); + } + + size_t limit_; + size_t active_; + std::mutex mtx_; + std::condition_variable cv_; + }; + + struct NetRequest::Impl + { + RequestOptions opts; + LogCallback logger; + Stats stats; + + // 并发控制 + ConcurrencyGate gate{4}; + + // 缓存 + struct CacheEntry + { + HttpResponse resp; + std::chrono::steady_clock::time_point expiry; + }; + bool cache_enabled = false; + std::chrono::milliseconds cache_ttl{0}; + std::unordered_map cache; + std::mutex cache_mtx; + + void log(const std::string &msg) + { + if (logger) logger(msg); + } + + template + void apply_client_options(ClientT &cli) + { + const time_t c_sec = static_cast(opts.connect_timeout_ms / 1000); + const time_t c_usec = static_cast((opts.connect_timeout_ms % 1000) * 1000); + const time_t r_sec = static_cast(opts.read_timeout_ms / 1000); + const time_t r_usec = static_cast((opts.read_timeout_ms % 1000) * 1000); + const time_t w_sec = static_cast(opts.write_timeout_ms / 1000); + const time_t w_usec = static_cast((opts.write_timeout_ms % 1000) * 1000); + cli.set_connection_timeout(c_sec, c_usec); + cli.set_read_timeout(r_sec, r_usec); + cli.set_write_timeout(w_sec, w_usec); + cli.set_keep_alive(opts.keep_alive); + } + + std::string build_full_path(const std::string &path) const + { + return joinPath(opts.base_path, path); + } + + std::string cache_key(const std::string &path, const httplib::Params ¶ms, const httplib::Headers &headers) + { + std::ostringstream oss; + oss << opts.scheme << "://" << opts.host << ':' << opts.port << build_full_path(path); + if (!params.empty()) oss << '?' << paramsToQuery(params); + for (auto &kv : headers) oss << '|' << kv.first << '=' << kv.second; + return oss.str(); + } + + void record_latency(double ms) + { + stats.last_latency_ms = ms; + const double alpha = 0.2; + if (stats.avg_latency_ms <= 0.0) stats.avg_latency_ms = ms; + else stats.avg_latency_ms = alpha * ms + (1.0 - alpha) * stats.avg_latency_ms; + } + + static ErrorCode map_error() + { + // 简化:无法区分具体错误码,统一归为 Network + return ErrorCode::Network; + } + }; + + NetRequest::NetRequest(const RequestOptions &options) + : impl_(new Impl) + { + impl_->opts = options; + if (impl_->opts.scheme == "https" && impl_->opts.port == 80) impl_->opts.port = 443; + if (impl_->opts.scheme == "http" && impl_->opts.port == 0) impl_->opts.port = 80; + } + + NetRequest::~NetRequest() + { + delete impl_; + } + + void NetRequest::setLogger(LogCallback logger) + { + impl_->logger = std::move(logger); + } + + void NetRequest::setMaxConcurrentRequests(size_t n) + { + impl_->gate.set_limit(n > 0 ? n : 1); + } + + void NetRequest::enableCache(std::chrono::milliseconds ttl) + { + impl_->cache_enabled = true; + impl_->cache_ttl = ttl.count() > 0 ? ttl : std::chrono::milliseconds(1000); + } + + void NetRequest::disableCache() + { + impl_->cache_enabled = false; + std::lock_guard lk(impl_->cache_mtx); + impl_->cache.clear(); + } + + ntq::optional NetRequest::Get(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, mergeHeaders(impl_->opts.default_headers, headers)); + std::lock_guard lk(impl_->cache_mtx); + auto it = impl_->cache.find(key); + if (it != impl_->cache.end() && std::chrono::steady_clock::now() < it->second.expiry) + { + if (err) *err = ErrorCode::None; + auto resp = it->second.resp; + resp.from_cache = true; + return resp; + } + } + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + impl_->log("HTTPS requested but OpenSSL is not enabled; falling back to error."); + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!result.has_value()) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, merged_headers); + std::lock_guard lk(impl_->cache_mtx); + impl_->cache[key] = Impl::CacheEntry{*result, std::chrono::steady_clock::now() + impl_->cache_ttl}; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + std::future> NetRequest::GetAsync(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, query, headers, err]() mutable { + ErrorCode local; + auto r = Get(path, query, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, json, headers, err]() mutable { + ErrorCode local; + auto r = PostJson(path, json, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, form, headers, err]() mutable { + ErrorCode local; + auto r = PostForm(path, form, headers, &local); + if (err) *err = local; + return r; + }); + } + + bool NetRequest::DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers, + bool resume, + size_t /*chunk_size*/, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + std::ios_base::openmode mode = std::ios::binary | std::ios::out; + size_t offset = 0; + if (resume) + { + std::ifstream in(local_file, std::ios::binary | std::ios::ate); + if (in) + { + offset = static_cast(in.tellg()); + } + mode |= std::ios::app; + } + else + { + mode |= std::ios::trunc; + } + + std::ofstream out(local_file, mode); + if (!out) + { + if (err) *err = ErrorCode::IOError; + impl_->stats.total_errors++; + return false; + } + + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + if (resume && offset > 0) + { + merged_headers.emplace("Range", "bytes=" + std::to_string(offset) + "-"); + } + + const auto full_path = impl_->build_full_path(path); + int status_code = 0; + ErrorCode local_err = ErrorCode::None; + + auto content_receiver = [&](const char *data, size_t data_length) { + out.write(data, static_cast(data_length)); + return static_cast(out); + }; + + bool ok = false; + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } + } + + out.close(); + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!ok) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return false; + } + if (err) *err = ErrorCode::None; + return true; + } + + NetRequest::Stats NetRequest::getStats() const + { + return impl_->stats; + } + + // ------------------------- Quick helpers ------------------------- + namespace { + struct ParsedURL { + std::string scheme; + std::string host; + int port = 0; + std::string path_and_query; + bool ok = false; + }; + + static ParsedURL parse_url(const std::string &url) + { + ParsedURL p; p.ok = false; + // very small parser: scheme://host[:port]/path[?query] + auto pos_scheme = url.find("://"); + if (pos_scheme == std::string::npos) return p; + p.scheme = url.substr(0, pos_scheme); + size_t pos_host = pos_scheme + 3; + + size_t pos_path = url.find('/', pos_host); + std::string hostport = pos_path == std::string::npos ? url.substr(pos_host) + : url.substr(pos_host, pos_path - pos_host); + auto pos_colon = hostport.find(':'); + if (pos_colon == std::string::npos) { + p.host = hostport; + p.port = (p.scheme == "https") ? 443 : 80; + } else { + p.host = hostport.substr(0, pos_colon); + std::string port_str = hostport.substr(pos_colon + 1); + p.port = port_str.empty() ? ((p.scheme == "https") ? 443 : 80) : std::atoi(port_str.c_str()); + } + p.path_and_query = (pos_path == std::string::npos) ? "/" : url.substr(pos_path); + p.ok = !p.host.empty(); + return p; + } + } + + ntq::optional NetRequest::QuickGet(const std::string &url, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.Get(p.path_and_query, {}, headers, err); + } + + ntq::optional NetRequest::QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostJson(p.path_and_query, json, headers, err); + } + + ntq::optional NetRequest::QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostForm(p.path_and_query, form, headers, err); + } +} diff --git a/softWareInit/NetraLib/src/Netra.cpp b/softWareInit/NetraLib/src/Netra.cpp new file mode 100644 index 0000000..a67282c --- /dev/null +++ b/softWareInit/NetraLib/src/Netra.cpp @@ -0,0 +1,693 @@ +#include "Netra.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TcpServer::TcpServer(int port) + : port_(port), running_(false), serverSock_(-1) {} + + /** + * @brief 析构函数中调用stop()确保服务器资源被释放 + */ + TcpServer::~TcpServer() + { + stop(); + } + + /** + * @brief 启动服务器: + * 1. 创建监听socket(TCP) + * 2. 绑定端口 + * 3. 监听端口 + * 4. 启动监听线程acceptThread_ + * + * @return 成功返回true,失败返回false + */ + bool TcpServer::start() + { + // 创建socket + serverSock_ = socket(AF_INET, SOCK_STREAM, 0); + if (serverSock_ < 0) + { + std::cerr << "Socket 创建失败\n"; + return false; + } + + // 设置socket地址结构 + sockaddr_in serverAddr; + std::memset(&serverAddr, 0, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port_); // 端口转网络字节序 + serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡IP + + // 允许端口重用,防止服务器异常关闭后端口被占用 + int opt = 1; + setsockopt(serverSock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + // 绑定端口 + if (bind(serverSock_, (sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) + { + std::cerr << "绑定失败\n"; + return false; + } + + // 开始监听,最大等待连接数为5 + if (listen(serverSock_, 5) < 0) + { + std::cerr << "监听失败\n"; + return false; + } + + // 设置运行标志为true + running_ = true; + + // 启动专门接受客户端连接的线程 + acceptThread_ = std::thread(&TcpServer::acceptClients, this); + + std::cout << "服务器启动,监听端口:" << port_ << std::endl; + return true; + } + + /** + * @brief 停止服务器: + * 1. 设置运行标志为false,通知线程退出 + * 2. 关闭监听socket + * 3. 关闭所有客户端socket,清理客户端列表 + * 4. 等待所有线程退出 + */ + void TcpServer::stop() + { + running_ = false; + + if (serverSock_ >= 0) + { + close(serverSock_); + serverSock_ = -1; + } + + { + // 线程安全关闭所有客户端socket + std::lock_guard lock(clientsMutex_); + for (int sock : clientSockets_) + { + close(sock); + } + clientSockets_.clear(); + } + + // 等待监听线程退出 + if (acceptThread_.joinable()) + acceptThread_.join(); + + // 等待所有客户端处理线程退出 + for (auto &t : clientThreads_) + { + if (t.joinable()) + t.join(); + } + + std::cout << "服务器已停止\n"; + } + + /** + * @brief acceptClients函数循环监听客户端连接请求 + * 每当accept成功: + * 1. 打印客户端IP和Socket信息 + * 2. 线程安全地将客户端Socket加入clientSockets_列表 + * 3. 创建新线程调用handleClient处理该客户端收发 + */ + void TcpServer::acceptClients() + { + while (running_) + { + sockaddr_in clientAddr; + socklen_t clientLen = sizeof(clientAddr); + int clientSock = accept(serverSock_, (sockaddr *)&clientAddr, &clientLen); + if (clientSock < 0) + { + if (running_) + std::cerr << "接受连接失败\n"; + continue; + } + + // 将客户端IP转换成字符串格式打印 + char clientIP[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(clientAddr.sin_addr), clientIP, INET_ADDRSTRLEN); + std::cout << "客户端连接,IP: " << clientIP << ", Socket: " << clientSock << std::endl; + + { + // 加锁保护共享的clientSockets_容器 + std::lock_guard lock(clientsMutex_); + clientSockets_.push_back(clientSock); + } + } + } + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端socket + * @param message 发送消息内容 + */ + void TcpServer::sendToClient(int clientSock, const std::string &message) + { + send(clientSock, message.c_str(), message.size(), 0); + } + + /** + * @brief 单次接收指定客户端数据 + * @param clientSock 客户端socket + */ + std::string TcpServer::receiveFromClient(int clientSock, bool flag) + { + char buffer[1024]; + std::memset(buffer, 0, sizeof(buffer)); + + int flags = flag ? 0 : MSG_DONTWAIT; + ssize_t bytesReceived = recv(clientSock, buffer, sizeof(buffer) - 1, flags); + + if (bytesReceived <= 0) + return {}; + + return std::string(buffer, bytesReceived); + } + + /** + * @brief 获取当前所有客户端Socket副本(线程安全) + * @return 包含所有客户端socket的vector副本 + */ + std::vector TcpServer::getClientSockets() + { + std::lock_guard lock(clientsMutex_); + return clientSockets_; + } + + void TcpServer::removeClient(int clientSock) + { + std::lock_guard lock(clientsMutex_); + for (auto it = clientSockets_.begin(); it != clientSockets_.end(); ++it) + { + if (*it == clientSock) + { + close(*it); + clientSockets_.erase(it); + break; + } + } + } + + bool TcpServer::isClientDisconnected(int clientSock) + { + char tmp; + ssize_t n = recv(clientSock, &tmp, 1, MSG_PEEK | MSG_DONTWAIT); + if (n == 0) + return true; // 对端有序关闭 + if (n < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return false; // 只是暂时无数据 + return true; // 其它错误视为断开 + } + return false; // 有数据可读 + } + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *TcpServer::getClientIPAndPort(int clientSock) + { + struct sockaddr_in addr; + socklen_t addr_size = sizeof(addr); + + // 获取客户端地址信息 + if (getpeername(clientSock, (struct sockaddr *)&addr, &addr_size) == -1) + { + perror("getpeername failed"); + return NULL; + } + + // 分配内存存储结果(格式: "IP:PORT") + char *result = (char *)malloc(INET_ADDRSTRLEN + 10); + if (!result) + return NULL; + + // 转换IP和端口 + char *ip = inet_ntoa(addr.sin_addr); + unsigned short port = ntohs(addr.sin_port); + + snprintf(result, INET_ADDRSTRLEN + 10, "%s:%d", ip, port); + return result; + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + WriteFile::WriteFile(const std::string &filePath) + : filePath_(filePath) {} + + /** + * @brief 覆盖写文本(线程安全) + */ + bool WriteFile::overwriteText(const std::string &content) + { + std::lock_guard lock(writeMutex_); // 加锁 + return writeToFile(content, std::ios::out | std::ios::trunc); + } + + /** + * @brief 追加写文本(线程安全) + */ + bool WriteFile::appendText(const std::string &content) + { + std::lock_guard lock(writeMutex_); + return writeToFile(content, std::ios::out | std::ios::app); + } + + /** + * @brief 覆盖写二进制(线程安全) + */ + bool WriteFile::overwriteBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::trunc | std::ios::binary); + } + + /** + * @brief 追加写二进制(线程安全) + */ + bool WriteFile::appendBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::app | std::ios::binary); + } + + /** + * @brief 通用文本写入(私有) + */ + bool WriteFile::writeToFile(const std::string &content, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file << content; + file.close(); + return true; + } + + /** + * @brief 通用二进制写入(私有) + */ + bool WriteFile::writeBinary(const std::vector &data, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file.write(data.data(), data.size()); + file.close(); + return true; + } + + size_t WriteFile::countBytesPattern(const std::string &pattern, bool includePattern) + { + std::lock_guard lock(writeMutex_); + + if (pattern.empty()) + return 0; + + std::ifstream file(filePath_, std::ios::binary); + if (!file.is_open()) + return 0; + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file.read(chunk, chunkSize) || file.gcount() > 0) + { + size_t bytesRead = file.gcount(); + buffer.append(chunk, bytesRead); + + size_t pos = buffer.find(pattern); + if (pos != std::string::npos) + { + size_t absolutePos = totalRead + pos; // 关键:加上 totalRead + return includePattern ? (absolutePos + pattern.size()) : absolutePos; + } + + if (buffer.size() > pattern.size()) + buffer.erase(0, buffer.size() - pattern.size()); + + totalRead += bytesRead; // 读完后再累计 + } + + return 0; + } + + bool WriteFile::writeAfterPatternOrAppend(const std::string &pattern, const std::string &content) + { + std::lock_guard lock(writeMutex_); + + // 读取整个文件 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + size_t pos = fileData.find(pattern); + if (pos != std::string::npos) + { + // 模式存在,插入位置在模式结尾 + pos += pattern.size(); + + // 删除模式后所有内容 + if (pos < fileData.size()) + fileData.erase(pos); + + // 插入新内容 + fileData.insert(pos, content); + } + else + { + // 模式不存在,直接追加到文件末尾 + if (!fileData.empty() && fileData.back() != '\n') + fileData += '\n'; // 保证换行 + fileData += content; + } + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::overwriteAtPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos >= fileData.size()) + return false; // pos 超过文件范围,无法覆盖 + + // 生成要覆盖的实际数据块 + std::string overwriteBlock; + if (content.size() >= length) + { + overwriteBlock = content.substr(0, length); + } + else + { + overwriteBlock = content; + overwriteBlock.append(length - content.size(), '\0'); // 补齐 + } + + // 计算实际可写范围 + size_t maxWritable = std::min(length, fileData.size() - pos); + + // 覆盖 + fileData.replace(pos, maxWritable, overwriteBlock.substr(0, maxWritable)); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::insertAfterPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos > fileData.size()) + pos = fileData.size(); // 如果 pos 超出范围,就视为文件末尾 + + // 生成要插入的实际数据块 + std::string insertBlock; + if (content.size() >= length) + { + insertBlock = content.substr(0, length); // 只取前 length 个字节 + } + else + { + insertBlock = content; // 全部内容 + insertBlock.append(length - content.size(), '\0'); // 补足空字节 + } + + // 插入到 pos 后面 + fileData.insert(pos + 1, insertBlock); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ReadFile::ReadFile(const std::string &filename) : filename_(filename) {} + + ReadFile::~ReadFile() + { + std::lock_guard lock(mtx_); + Close(); + } + + bool ReadFile::Open() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + file_.close(); + file_.open(filename_, std::ios::in | std::ios::binary); + return file_.is_open(); + } + + void ReadFile::Close() + { + if (file_.is_open()) + { + std::lock_guard lock(mtx_); + file_.close(); + } + } + + bool ReadFile::IsOpen() const + { + std::lock_guard lock(mtx_); + return file_.is_open(); + } + + std::string ReadFile::ReadAllText() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return ""; + + std::ostringstream ss; + ss << file_.rdbuf(); + return ss.str(); + } + + std::vector ReadFile::ReadAllBinary() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + return ReadBytes(GetFileSize()); + } + + std::vector ReadFile::ReadLines() + { + // std::lock_guard lock(mtx_); + // if (!file_.is_open() && !Open()) + // return {}; + + // std::vector lines; + // std::string line; + // while (std::getline(file_, line)) + // { + // lines.push_back(line); + // } + // return lines; + + // std::lock_guard lock(mtx_); + if (!file_.is_open()) { + file_.open(filename_, std::ios::in | std::ios::binary); + if (!file_.is_open()) return {}; + } + file_.clear(); + file_.seekg(0, std::ios::beg); + + std::vector lines; + std::string line; + while (std::getline(file_, line)) lines.push_back(line); + return lines; + } + + std::vector ReadFile::ReadBytes(size_t count) + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + std::vector buffer(count); + file_.read(buffer.data(), count); + buffer.resize(file_.gcount()); + return buffer; + } + + size_t ReadFile::GetBytesBefore(const std::string &marker, bool includeMarker) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return 0; + + file_.clear(); // 清除EOF和错误状态 + file_.seekg(0, std::ios::beg); // 回到文件开头 + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file_.read(chunk, chunkSize) || file_.gcount() > 0) + { + buffer.append(chunk, file_.gcount()); + size_t pos = buffer.find(marker); + if (pos != std::string::npos) + { + // 如果 includeMarker 为 true,返回包含 marker 的长度 + if (includeMarker) + return pos + marker.size(); + else + return pos; + } + + // 保留末尾部分,避免 buffer 无限增长 + if (buffer.size() > marker.size()) + buffer.erase(0, buffer.size() - marker.size()); + + totalRead += file_.gcount(); + } + + return 0; + } + + std::vector ReadFile::ReadBytesFrom(size_t pos, size_t count) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return {}; + + size_t filesize = GetFileSize(); + if (pos >= filesize) + return {}; // 起始位置超出文件大小 + + file_.clear(); // 清除 EOF 和错误状态 + file_.seekg(pos, std::ios::beg); + if (!file_) + return {}; + + size_t bytes_to_read = count; + if (count == 0 || pos + count > filesize) + bytes_to_read = filesize - pos; // 读取到文件末尾 + + std::vector buffer(bytes_to_read); + file_.read(buffer.data(), bytes_to_read); + + // 实际读取的字节数可能少于请求的数量 + buffer.resize(file_.gcount()); + + return buffer; + } + + bool ReadFile::FileExists() const + { + return std::filesystem::exists(filename_); + } + + size_t ReadFile::GetFileSize() const + { + if (!FileExists()) + return 0; + return std::filesystem::file_size(filename_); + } + + void ReadFile::Reset() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + { + file_.clear(); + file_.seekg(0, std::ios::beg); + } + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals() + { + // 忽略全部的信号 + for (int ii = 1; ii <= 64; ii++) + signal(ii, SIG_IGN); + } + + std::string Ltrim(const std::string &s) + { + size_t start = 0; + while (start < s.size() && std::isspace(static_cast(s[start]))) + { + ++start; + } + return s.substr(start); + } + + std::string Rtrim(const std::string &s) + { + if (s.empty()) + return s; + + size_t end = s.size(); + while (end > 0 && std::isspace(static_cast(s[end - 1]))) + { + --end; + } + return s.substr(0, end); + } + + std::string LRtrim(const std::string &s) + { + return Ltrim(Rtrim(s)); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} \ No newline at end of file diff --git a/softWareInit/NetraLib/src/encrypt.cpp b/softWareInit/NetraLib/src/encrypt.cpp new file mode 100644 index 0000000..a8aea93 --- /dev/null +++ b/softWareInit/NetraLib/src/encrypt.cpp @@ -0,0 +1,91 @@ +#include "encrypt.hpp" +#include + +namespace encrypt +{ + string MD5(const string &info) + { + auto leftrotate = [](uint32_t x, uint32_t c) -> uint32_t { return (x << c) | (x >> (32 - c)); }; + + static const uint32_t s[64] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + static const uint32_t K[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + uint32_t a0 = 0x67452301; + uint32_t b0 = 0xefcdab89; + uint32_t c0 = 0x98badcfe; + uint32_t d0 = 0x10325476; + + std::vector msg(info.begin(), info.end()); + uint64_t bit_len = static_cast(msg.size()) * 8ULL; + msg.push_back(0x80); + while ((msg.size() % 64) != 56) msg.push_back(0x00); + for (int i = 0; i < 8; ++i) msg.push_back(static_cast((bit_len >> (8 * i)) & 0xff)); + + for (size_t offset = 0; offset < msg.size(); offset += 64) + { + uint32_t M[16]; + for (int i = 0; i < 16; ++i) + { + size_t j = offset + i * 4; + M[i] = static_cast(msg[j]) | + (static_cast(msg[j + 1]) << 8) | + (static_cast(msg[j + 2]) << 16) | + (static_cast(msg[j + 3]) << 24); + } + + uint32_t A = a0, B = b0, C = c0, D = d0; + for (uint32_t i = 0; i < 64; ++i) + { + uint32_t F, g; + if (i < 16) { F = (B & C) | ((~B) & D); g = i; } + else if (i < 32) { F = (D & B) | ((~D) & C); g = (5 * i + 1) % 16; } + else if (i < 48) { F = B ^ C ^ D; g = (3 * i + 5) % 16; } + else { F = C ^ (B | (~D)); g = (7 * i) % 16; } + + F = F + A + K[i] + M[g]; + A = D; + D = C; + C = B; + B = B + leftrotate(F, s[i]); + } + + a0 += A; b0 += B; c0 += C; d0 += D; + } + + uint8_t digest[16]; + auto u32_to_le = [](uint32_t v, uint8_t out[4]) { + out[0] = static_cast(v & 0xff); + out[1] = static_cast((v >> 8) & 0xff); + out[2] = static_cast((v >> 16) & 0xff); + out[3] = static_cast((v >> 24) & 0xff); + }; + u32_to_le(a0, digest + 0); + u32_to_le(b0, digest + 4); + u32_to_le(c0, digest + 8); + u32_to_le(d0, digest + 12); + + static const char *hex = "0123456789abcdef"; + std::string out; + out.resize(32); + for (int i = 0; i < 16; ++i) + { + out[i * 2] = hex[(digest[i] >> 4) & 0x0f]; + out[i * 2 + 1] = hex[digest[i] & 0x0f]; + } + return out; + } +} \ No newline at end of file diff --git a/softWareInit/bin/verification b/softWareInit/bin/verification new file mode 100755 index 0000000..6cc39dd Binary files /dev/null and b/softWareInit/bin/verification differ diff --git a/softWareInit/rtsp_ffmpeg.err.log b/softWareInit/rtsp_ffmpeg.err.log new file mode 100644 index 0000000..e69de29 diff --git a/softWareInit/src/main.cpp b/softWareInit/src/main.cpp new file mode 100644 index 0000000..6d2f77a --- /dev/null +++ b/softWareInit/src/main.cpp @@ -0,0 +1,655 @@ +/* +本程序用于设备验证 +验证通过执行以下功能: +1.启动RTSP推流 +2.启动上传服务,用于上传最新报警图片 +3.启动mqtt,与App进行数据交互 +认证失败则会退出程序 +*/ + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include "Netra.hpp" +#include "NetRequest.hpp" +#include "encrypt.hpp" + +using namespace std; +using namespace QCL; +using namespace ntq; +using namespace encrypt; +using namespace chrono_literals; +namespace bp = boost::process; + +bp::group g_group_proc; + +bp::child rtsp_proc; // 服务器进程 +bp::child video_proc; // 视频推流进程 +bp::child net_proc; // 开启网络进程 +bp::child camera_proc; // 摄像头线程 + +// 文件设置路径 +string filepath = "/home/orangepi/RKApp/InitAuth/conf/.env"; +// string filepath = "/opt/rknn-yolov11/.env"; +// string passwd = "/home/orangepi/InitAuth/pwd/.env"; + +// 云端Web认证接口 +const string url = "http://116.147.36.110:8095/device/validateDevice"; +const string mqtt_url = "tcp://192.168.12.1:1883"; +const string clientId = "RK3588_SubTest"; +const string Topic = "/bsd_camera/cmd"; +const int Qos = 1; + +std::atomic isRunning(true); // 全局运行标志 +std::atomic confirm(true); // 发送卡和ID +condition_variable runCv; +mutex runMutex; +mutex envMutex; // 配置锁 + +// 媒体对象 +struct Media +{ + bool mirror; // 镜像 + bool flip; // 翻转 + bool occlusion; // 遮挡 +} media{}; + +// 输入输出对象 +struct OutSignal +{ + bool outPutMode; // 警报输出电平 true--高电平,false -- 低电平 + bool inPutMode; // 触发输入模式 true--高电平,false -- 低电平 +} algor{}; + +struct Point +{ + double x, y; +}; + +struct ZoneBox +{ + string name; + array vertices; +}; + +// 获取SIM卡号 +string GetSimICCID(const string &tty = "/dev/ttyUSB2"); +string Getqrcode(); + +// 开始进行设备验证 +bool verification(); + +// 解析接收的数据 +template +void CalculateInfo(T &conf, const string &json); + +// 创建服务器 +// mqtt client +mqtt::async_client client(mqtt_url, clientId); + +// 启动摄像头 +void startCamera(); + +// 退出信号捕捉 +void Exit(int sig); + +// 开启网络(4G模块) +void StartNet(); + +// 开启服务 +void StartService(); + +// mqtt初始化 +void mqttInit(); + +// 接收消息回调 +void getMsgCallback(mqtt::const_message_ptr msg); + +// 解析四个点的坐标 +vector pareseZone(const nlohmann::json &j); + +// 写回配置文件,距离和坐标统一处理 +void updateConfig(const double danger, const double warn, const double safe, const vector &boxes); + +int main(int argc, char *argv[]) +{ + signal(SIGINT, Exit); // 捕获Ctrl+C信号 + + // 开启验证 + if (verification() == false) + { + // 验证失败,退出程序 + cerr << "验证失败" << endl; + return 0; + } + + // 初始化mqtt + mqttInit(); + + // 开启系统服务(上传图片服务,虚拟摄像头) + thread(StartService).detach(); + + this_thread::sleep_for(10s); + + cout << "开启摄像头" << endl; + + // 开启摄像头 + startCamera(); + // 等待摄像头进程启动并开始编码发送,再启动 RTSP 转推 + std::this_thread::sleep_for(std::chrono::seconds(2)); + + unique_lock lk(runMutex); + runCv.wait(lk, []() + { return !isRunning; }); + + return 0; +} + +// 写回配置文件,距离和坐标统一处理 +void updateConfig(const double danger, const double warn, const double safe, const vector &boxes) +{ + lock_guard lk(envMutex); + ReadFile rf(filepath); + if (!rf.Open()) + { + cerr << "文件打开失败: " << filepath << "\n"; + return; + } + + auto lines = rf.ReadLines(); + rf.Close(); + + auto setLine = [&](const string &key, const string &value) + { + bool found = false; + for (auto &line : lines) + { + if (line.rfind(key + "=", 0) == 0) + { + line = QCL::format("{}={}", key, value); + found = true; + break; + } + } + if (!found) + lines.push_back(QCL::format("{}={}", key, value)); // 不存在则追加 + }; + + // 距离(保持不变) + setLine("NEAR_THRESHOLD", QCL::format("{}", danger)); + setLine("MID_THRESHOLD", QCL::format("{}", warn)); + setLine("MAX_DISTANCE", QCL::format("{}", safe)); + + // 坐标:改为写入归一化值(沿用原键名 SAFE_1_X/SAFE_1_Y 等,但值为小数) + auto upKey = [](const string &name) + { + if (name == "safe") + return string("SAFE"); + if (name == "warning") + return string("WARN"); + return string("DANG"); + }; + + for (const auto &box : boxes) + { + string prefix = upKey(box.name); + for (int i = 0; i < 4; ++i) + { + setLine(QCL::format("{}_{}_X", prefix, i + 1), QCL::format("{}", box.vertices[i].x)); + setLine(QCL::format("{}_{}_Y", prefix, i + 1), QCL::format("{}", box.vertices[i].y)); + } + } + + // 写回 + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) + { + out += lines[i]; + if (i + 1 < lines.size()) + out += "\n"; + } + WriteFile wf(filepath); + wf.overwriteText(out); +} + +// 解析四个点的坐标 +vector pareseZone(const nlohmann::json &j) +{ + vector boxes; + if (!j.contains("params") || !j["params"].contains("zones")) + return boxes; + + for (const auto &z : j["params"]["zones"]) + { + ZoneBox box; + box.name = z.value("zone", ""); + const auto &v = z["vertices"]; + if (v.is_array() && v.size() >= 4) + { + for (int i = 0; i < 4; ++i) + { + // 读取归一化坐标(默认 0.0) + box.vertices[i].x = v[i].value("x_norm", 0.0); + box.vertices[i].y = v[i].value("y_norm", 0.0); + } + } + boxes.push_back(std::move(box)); + } + return boxes; +} + +// 启动摄像头 +void startCamera() +{ + string cmd = "/home/orangepi/RKApp/VideoProsessing/bin/video"; + try + { + camera_proc = bp::child(cmd, g_group_proc); + // cout << "camera_pid = " << camera_proc.id() << endl; + } + catch (...) + { + cerr << "摄像头启动失败" << endl; + } +} + +// 初始化MQTT +void mqttInit() +{ + + client.set_connected_handler([](const string &cause) + { cout << "Connected Successed!\n"; }); + client.set_message_callback(getMsgCallback); + + // 连接服务器 + client.connect()->wait(); + client.subscribe(Topic, Qos)->wait(); +} + +// 接收消息回调 +void getMsgCallback(mqtt::const_message_ptr msg) +{ + // 立即拷贝负载,避免在回调里做大量工作 + std::string payload = msg->to_string(); + + std::thread([payload]() + { + try + { + string buffer = payload; + if (buffer.empty()) + return; + // SET_DISTANCES: 解析 JSON 并更新 filepath 文件 + if (buffer.find("SET_DISTANCES") != string::npos) + { //获取报警距离 + auto j = nlohmann::json::parse(buffer); + + auto toDouble = [](const nlohmann::json &jv) -> double { + if (jv.is_number()) return jv.get(); + if (jv.is_string()) { try { return std::stod(jv.get()); } catch (...) { return 0.0; } } + return 0.0; + }; + + double danger = toDouble(j["params"]["danger_distance"]); + double warn = toDouble(j["params"]["warning_distance"]); + double safe = toDouble(j["params"]["safe_distance"]); + + auto boxes = pareseZone(j); + updateConfig(danger, warn, safe, boxes); + return; + } + // media: 更新 filepath 的 MEDIA_* 设置 + else if (buffer.find("media") != string::npos) + {//设置摄像头参数 + CalculateInfo(media, buffer); + ReadFile rf(filepath); + if (!rf.Open()) { + cerr << "文件打开失败: " << filepath << "\n"; + } else { + auto lines = rf.ReadLines(); + for (auto &line : lines) { + //距离信息 + if (line.rfind("MEDIA_MIRROR=", 0) == 0) line = format("MEDIA_MIRROR={}", media.mirror ? "true" : "false"); + else if (line.rfind("MEDIA_FLIP=", 0) == 0) line = format("MEDIA_FLIP={}", media.flip ? "true" : "false"); + else if (line.rfind("MEDIA_OCCLUSION=", 0) == 0) line = format("MEDIA_OCCLUSION={}", media.occlusion ? "true" : "false"); + } + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) { + out += lines[i]; + if (i + 1 < lines.size()) out += "\n"; + } + rf.Close(); + WriteFile wf(filepath); + wf.overwriteText(out); + } + return; + } + // algorithm: 更新 filepath 中的 outPutMode/inPutMode + if (buffer.find("algorithm") != string::npos) + {//设置报警输出模式 + CalculateInfo(algor, buffer); + ReadFile rf2(filepath); + if (!rf2.Open()) { + cerr << "文件打开失败: " << filepath << "\n"; + } else { + auto lines = rf2.ReadLines(); + for (auto &line : lines) { + if (line.rfind("outPutMode:", 0) == 0) line = format("outPutMode:{}", algor.outPutMode ? "true" : "false"); + else if (line.rfind("inPutMode:", 0) == 0) line = format("inPutMode:{}", algor.inPutMode ? "true" : "false"); + } + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) { + out += lines[i]; + if (i + 1 < lines.size()) out += "\n"; + } + rf2.Close(); + WriteFile wf2(filepath); + wf2.overwriteText(out); + } + return; + } + } + catch (const std::exception &e) + { + std::cerr << "处理 MQTT 消息异常: " << e.what() << std::endl; + } + catch (...) + { + std::cerr << "处理 MQTT 消息时发生未知错误\n"; + } }) + .detach(); +} + +// 开启服务:fastApi,pub +void StartService() +{ + string commnd = "echo 'orangepi' | sudo -S -E env PATH=$PATH ../../StartService/bin/start start"; + system(commnd.c_str()); +} + +// 开启网络 +void StartNet() +{ + string commd = "../../GetNet/bin/setNet"; + net_proc = bp::child("/bin/bash", bp::args = {"-c", commd}, g_group_proc); +} + +// 进行设备验证 +bool verification() +{ + bool flag = false; // 是否通过验证 + + // 获取ICCID + string ICCID = GetSimICCID(); + // 获取唯一标识符 + string qrcode = Getqrcode(); + string str = format("{}@{}", ICCID, qrcode); + // 实时加密 + string pass = MD5(str); + + // 获取ICCID + string request = format(R"({"cardNo":"{}","qrcode":"{}"})", ICCID, qrcode); + + auto res = NetRequest::QuickPostJson(url, request); + + if (res->status == 200) + { + // 线上认证 + auto json = nlohmann::json::parse(res->body); + if (json["code"] == 200) + { // 验证成功 + flag = true; + } + } + else + { + // 线下验证 + ReadFile rf(filepath); + if (rf.Open() == false) + return false; + auto lines = rf.ReadLines(); + + string str; + + for (auto &ii : lines) + { + if (ii.find("ServerPwd:") != string::npos) + { + str = ii.substr(sizeof("ServerPwd")); + break; + } + } + pass = format(R"("{}")", pass); + if (str == pass) + { // 验证成功 + flag = true; + } + + rf.Close(); + } + + return flag; +} + +// 退出信号 +void Exit(int sig) +{ + cout << "Exiting..." << endl; + + // 终止子进程,避免继续输出 + try + { + if (g_group_proc.valid()) + { + g_group_proc.terminate(); + } + } + catch (const exception &e) + { + cerr << "终止进程组失败: " << e.what() << endl; + } + + isRunning = false; + runCv.notify_all(); + + cout << "System Quit" << endl; +} + +// 获取SIM卡号 +// 通过串口发送 AT+CCID 指令,读取并解析返回的ICCID号 +string GetSimICCID(const string &tty) +{ + int retry = 0; + while (retry < 5) + { + // 非阻塞打开,避免因 VMIN/VTIME 卡死 + int fd = open(tty.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); + if (fd < 0) + { + std::cerr << "无法打开串口: " << tty << std::endl; + return ""; + } + + // 原始模式配置 + struct termios options{}; + tcgetattr(fd, &options); + cfsetispeed(&options, B115200); + cfsetospeed(&options, B115200); + options.c_cflag |= (CLOCAL | CREAD); + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_oflag &= ~OPOST; + options.c_iflag &= ~(IXON | IXOFF | IXANY); + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = 5; // 0.5s + tcsetattr(fd, TCSANOW, &options); + + auto send_and_read = [&](const char *cmd) -> std::string + { + // 清空缓冲并发送 + tcflush(fd, TCIOFLUSH); + write(fd, cmd, strlen(cmd)); + std::string result; + char buf[256] = {0}; + // 轮询读取,累计约2秒 + for (int i = 0; i < 20; ++i) + { + int n = read(fd, buf, sizeof(buf)); + if (n > 0) + result.append(buf, n); + usleep(100000); + } + return result; + }; + + // 先试 AT+QCCID,失败再试 AT+CCID + std::string result = send_and_read("AT+QCCID\r\n"); + if (result.find("+QCCID") == std::string::npos) + { + std::string r2 = send_and_read("AT+CCID\r\n"); + if (!r2.empty()) + result += r2; + } + close(fd); + + // 打印原始回应便于调试 + std::string debug = result; + // 清理换行 + debug.erase(std::remove_if(debug.begin(), debug.end(), + [](unsigned char c) + { return c == '\r' || c == '\n'; }), + debug.end()); + // std::cout << "ICCID原始回应: " << debug << std::endl; + + // 错误重试 + if (result.find("ERROR") != std::string::npos) + { + retry++; + usleep(200000); + continue; + } + + // 解析(支持字母数字) + std::smatch m; + // +QCCID 或 +CCID 后取字母数字 + std::regex reg(R"(\+(?:Q)?CCID:\s*([0-9A-Za-z]+))"); + if (std::regex_search(debug, m, reg)) + { + std::string iccid = m[1]; + // 去掉尾部OK或非字母数字 + while (!iccid.empty() && !std::isalnum(static_cast(iccid.back()))) + iccid.pop_back(); + if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK") + iccid.erase(iccid.size() - 2); + return iccid; + } + + // 兜底:19~22位的字母数字(如尾部含 D) + std::regex reg2(R"(([0-9A-Za-z]{19,22}))"); + if (std::regex_search(debug, m, reg2)) + { + std::string iccid = m[1]; + while (!iccid.empty() && !std::isalnum(static_cast(iccid.back()))) + iccid.pop_back(); + if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK") + iccid.erase(iccid.size() - 2); + return iccid; + } + + // 进一步兜底:手工截取 +QCCID: / +CCID: 后的连续字母数字 + auto parse_after = [&](const std::string &s, const std::string &key) -> std::string + { + size_t pos = s.find(key); + if (pos == std::string::npos) + return ""; + pos += key.size(); + while (pos < s.size() && std::isspace(static_cast(s[pos]))) + pos++; + size_t start = pos; + while (pos < s.size() && std::isalnum(static_cast(s[pos]))) + pos++; + std::string iccid = (pos > start) ? s.substr(start, pos - start) : ""; + if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK") + iccid.erase(iccid.size() - 2); + return iccid; + }; + { + std::string iccid = parse_after(debug, "+QCCID:"); + if (iccid.empty()) + iccid = parse_after(debug, "+CCID:"); + if (!iccid.empty()) + return iccid; + } + + retry++; + usleep(200000); + } + return ""; +} + +// 读取文件获取唯一标识符 +string Getqrcode() +{ + lock_guard lk(envMutex); + ReadFile rf(filepath); + rf.Open(); + auto lines = rf.ReadLines(); + rf.Close(); + for (auto &line : lines) + { + if (line.find("UUID:") != string::npos) + { + size_t start = sizeof("UUID:\"") - 1; + size_t end = line.find_last_of("\"") - start; + return line.substr(start, end); + } + } + + return ""; +} + +// 解析接收的数据 +template +void CalculateInfo(T &conf, const string &json) +{ + try + { + /* code */ + auto j = nlohmann::json::parse(json); + if (j.contains("params") && j["params"].contains("media")) + { + auto conf = j["params"]["media"]; + if (conf.contains("mirror")) + media.mirror = conf["mirror"].get(); + if (conf.contains("flip")) + media.flip = conf["flip"].get(); + if (conf.contains("occlusion")) + media.occlusion = conf["occlusion"].get(); + } + else if (j.contains("params") && j["params"].contains("algorithm")) + { + auto conf = j["params"]["algorithm"]; + if (conf.contains("alarm_output_mode")) + algor.outPutMode = (conf["alarm_output_mode"].get().find("高电平") != string::npos) ? true : false; + + if (conf.contains("trigger_input_mode")) + algor.inPutMode = (conf["trigger_input_mode"].get().find("高电平") != string::npos) ? true : false; + } + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } +} diff --git a/softWareInit/src/main.cpp.bak b/softWareInit/src/main.cpp.bak new file mode 100644 index 0000000..3918d25 --- /dev/null +++ b/softWareInit/src/main.cpp.bak @@ -0,0 +1,577 @@ +/* +本程序提供以下功能: +1.开启4G模块,允许上网 +2.进行RTSP推流 +3.初始化验证->启动系统服务 +*/ + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "Netra.hpp" +#include "NetRequest.hpp" +#include "encrypt.hpp" + +using namespace std; +using namespace QCL; +using namespace ntq; +using namespace encrypt; +using namespace chrono_literals; +namespace bp = boost::process; + +bp::child rtsp_proc; // 服务器进程 +bp::child video_proc; // 视频推流进程 +bp::child net_proc; // 开启网络进程 + +// 文件设置路径 +string filepath = "/home/orangepi/InitAuth/conf/.env"; +string cameraPath = "/opt/rknn-yolov11/.env"; +string passwd = "/home/orangepi/InitAuth/pwd/.env"; + +// 云端Web认证接口 +const string url = "http://116.147.36.110:8095/device/validateDevice"; +const string mqtt_url = "tcp://192.168.12.1:1883"; +const string clientId = "RK3588_SubTest"; +const string Topic = "/bsd_camera/cmd"; +const int Qos = 1; + +std::atomic isRunning(true); // 全局运行标志 +std::atomic confirm(true); // 发送卡和ID + +// 媒体对象 +struct Media +{ + bool mirror; // 镜像 + bool flip; // 翻转 + bool occlusion; // 遮挡 +} media{}; + +// 输入输出对象 +struct OutSignal +{ + bool outPutMode; // 警报输出电平 true--高电平,false -- 低电平 + bool inPutMode; // 触发输入模式 true--高电平,false -- 低电平 +} algor{}; + +// 确认是否已经进行过认证 +bool ConfirmInit(); + +// 获取cpu序列号和SIM卡号 +string GetCardInfo(); + +// 获取SIM卡号 +string GetSimICCID(const string &tty = "/dev/ttyUSB2"); + +// 发送cpu序列号和SIM卡号 +void SendCardInfo(mqtt::async_client &client); + +// 进行验证 +bool verification(); + +// 解析接收的数据 +template +void CalculateInfo(T &conf, const string &json); + +// 创建服务器 +// TcpServer *MyServer; +// mqtt client +mqtt::async_client client(mqtt_url, clientId); + +// 打开RSTP服务器 +void OpenRTSP(); + +// 视频推流 +void VideoStream(); + +// 退出信号捕捉 +void Exit(int sig); + +// 开启网络(4G模块) +void StartNet(); + +// 开启服务 +void StartService(); + +// mqtt初始化 +void mqttInit(); + +// 接收消息回调 +void getMsgCallback(mqtt::const_message_ptr msg); + +/* +parames: +argv[1] - SSID of the WiFi network to connect to +argv[2] - Password for the WiFi network (if applicable) +*/ +int main(int argc, char *argv[]) +{ + // 开启4G模块 + StartNet(); + + this_thread::sleep_for(chrono::seconds(2)); + + // 关闭全部信号 + blockAllSignals(); + signal(SIGINT, Exit); // 捕获Ctrl+C信号 + + // 初始化mqtt服务器 + mqttInit(); + + // // 开启服务器 + // 如果没有进行过初始化,发送物联网卡信息进行认证 + if (ConfirmInit() == false) + { + SendCardInfo(client); + } + else + { + if (verification() == false) + { + cerr << "验证失败" << endl; + return 0; + } + } + + // 开启服务 + thread(StartService).detach(); + + thread Rtsp(OpenRTSP); + std::this_thread::sleep_for(chrono::seconds(2)); // 等待RTSP服务器启动 + + // 进行RTSP视频推流 + thread video(VideoStream); + + while (isRunning) + { + this_thread::sleep_for(1s); + } + + // Processing.join(); + video.join(); + Rtsp.join(); + + return 0; +} + +// 初始化MQTT +void mqttInit() +{ + + client.set_connected_handler([](const string &cause) + { cout << "Connected Successed!\n"; }); + client.set_message_callback(getMsgCallback); + + // 连接服务器 + client.connect()->wait(); + client.subscribe(Topic, Qos)->wait(); +} + +// 接收消息回调 +void getMsgCallback(mqtt::const_message_ptr msg) +{ + // 立即拷贝负载,避免在回调里做大量工作 + std::string payload = msg->to_string(); + + cout << "Recv:" << payload << endl; + + std::thread([payload]() + { + try + { + string buffer = payload; + if (buffer.empty()) + return; + + // Pass: 更新 Init 标志并写入密码文件 + if (buffer.find("Pass:") != string::npos) + { + WriteFile wf(filepath); + string str = "yse\n"; + wf.overwriteAtPos(str, wf.countBytesPattern("InitOrNot:", true), str.size()); + + WriteFile wf2(passwd); + wf2.overwriteText(buffer.substr(5)); + return; + } + + // SET_DISTANCES: 解析 JSON 并更新 cameraPath 文件 + if (buffer.find("SET_DISTANCES") != string::npos) + { + auto res = nlohmann::json::parse(buffer); + auto &danger_json = res["params"]["danger_distance"]; + auto &warning_json = res["params"]["warning_distance"]; + auto &safe_json = res["params"]["safe_distance"]; + + auto toDouble = [](const nlohmann::json &jv) -> double { + if (jv.is_number()) return jv.get(); + if (jv.is_string()) { + try { return std::stod(jv.get()); } + catch (...) { return 0.0; } + } + return 0.0; + }; + + double danger = toDouble(danger_json); + double warn = toDouble(warning_json); + double safe = toDouble(safe_json); + + ReadFile rf(cameraPath); + if (!rf.Open()) { + cerr << "文件打开失败: " << cameraPath << "\n"; + } else { + auto lines = rf.ReadLines(); + for (auto &line : lines) { + if (line.rfind("NEAR_THRESHOLD=", 0) == 0) line = format("NEAR_THRESHOLD={}", danger); + else if (line.rfind("MID_THRESHOLD=", 0) == 0) line = format("MID_THRESHOLD={}", warn); + else if (line.rfind("MAX_DISTANCE=", 0) == 0) line = format("MAX_DISTANCE={}", safe); + } + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) { + out += lines[i]; + if (i + 1 < lines.size()) out += "\n"; + } + WriteFile wf(cameraPath); + wf.overwriteText(out); + } + return; + } + + // media: 更新 cameraPath 的 MEDIA_* 设置 + if (buffer.find("media") != string::npos) + { + CalculateInfo(media, buffer); + ReadFile rf(cameraPath); + if (!rf.Open()) { + cerr << "文件打开失败: " << cameraPath << "\n"; + } else { + auto lines = rf.ReadLines(); + for (auto &line : lines) { + if (line.rfind("MEDIA_MIRROR=", 0) == 0) line = format("MEDIA_MIRROR={}", media.mirror ? "true" : "false"); + else if (line.rfind("MEDIA_FLIP=", 0) == 0) line = format("MEDIA_FLIP={}", media.flip ? "true" : "false"); + else if (line.rfind("MEDIA_OCCLUSION=", 0) == 0) line = format("MEDIA_OCCLUSION={}", media.occlusion ? "true" : "false"); + } + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) { + out += lines[i]; + if (i + 1 < lines.size()) out += "\n"; + } + WriteFile wf(cameraPath); + wf.overwriteText(out); + } + return; + } + + // algorithm: 更新 filepath 中的 outPutMode/inPutMode + if (buffer.find("algorithm") != string::npos) + { + CalculateInfo(algor, buffer); + ReadFile rf2(filepath); + if (!rf2.Open()) { + cerr << "文件打开失败: " << filepath << "\n"; + } else { + auto lines = rf2.ReadLines(); + for (auto &line : lines) { + if (line.rfind("outPutMode:", 0) == 0) line = format("outPutMode:{}", algor.outPutMode ? "true" : "false"); + else if (line.rfind("inPutMode:", 0) == 0) line = format("inPutMode:{}", algor.inPutMode ? "true" : "false"); + } + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) { + out += lines[i]; + if (i + 1 < lines.size()) out += "\n"; + } + WriteFile wf2(filepath); + wf2.overwriteText(out); + } + return; + } + } + catch (const std::exception &e) + { + std::cerr << "处理 MQTT 消息异常: " << e.what() << std::endl; + } + catch (...) + { + std::cerr << "处理 MQTT 消息时发生未知错误\n"; + } }) + .detach(); +} + +// 开启服务:fastApi,pub +void StartService() +{ + string commnd = "echo 'orangepi' | sudo -S ../../StartService/bin/start start"; + system(commnd.c_str()); +} + +// 开启网络 +void StartNet() +{ + string commd = "../../GetNet/bin/setNet"; + net_proc = bp::child("/bin/bash", bp::args = {"-c", commd}); +} + +// 进行验证 +bool verification() +{ + // 读取文件密文 + ReadFile *rf = new ReadFile(passwd); + bool flag = false; + auto pass = rf->ReadLines(); + // 若为空:改写初始化标志位false,重认证 + if (pass.empty()) + { + WriteFile *wf = new WriteFile(filepath); + wf->overwriteAtPos("no", wf->countBytesPattern("InitOrNot:", true), 3); + return 0; + } + + for (auto &ii : pass) + { + // 不为空,线上发送密文开始认证 + auto pos = format(R"({{"cardNo":"{}"}})", ii); + + auto res = NetRequest::QuickPostJson(url, pos); + + int code = -1; + if (res) + { + try + { + code = nlohmann::json::parse(res->body).value("code", -1); + } + catch (const std::exception &e) + { + std::cerr << "JSON解析失败: " << e.what() << std::endl; + } + } + + if (res && code == 200) + { + cout << "线上:认证成功" << endl; + flag = true; + } + else + { + try + { + // 进行线下认证 + // 获取cpu序列号和SIM卡号 + string CardID = GetCardInfo(); + string SIMID = GetSimICCID(); + string info = CardID + SIMID; + // 进行MD5加密 + string md5 = MD5(info); + cout << md5 << endl; + if (md5.compare(ii) == 0) + { + cout << "线下:认证成功" << endl; + flag = true; + } + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } + } + } + cout << "结束" << endl; + return flag; +} + +// 退出信号 +void Exit(int sig) +{ + cout << "Exiting..." << endl; + isRunning = false; + confirm = false; + // MyServer->stop(); + + system("sudo killall create_ap"); + // 杀死后台 RTSP 和 ffmpeg 进程 + if (rtsp_proc.running()) + rtsp_proc.terminate(); + if (video_proc.running()) + video_proc.terminate(); + if (net_proc.running()) + net_proc.terminate(); +} + +// 打开RSTP服务器 +void OpenRTSP() +{ + string commnd = "../RTSPServer/mediamtx ../RTSPServer/mediamtx.yml"; + rtsp_proc = bp::child("/bin/bash", bp::args = {"-c", commnd}); +} + +// 视频推流 +void VideoStream() +{ + // 静音ffmpeg的统计输出,保留错误;避免污染日志 + string commnd = "ffmpeg -nostats -hide_banner -loglevel error -f v4l2 -i /dev/video0 -c:v h264_rkmpp -rtsp_transport tcp -f rtsp rtsp://192.168.12.1:8554/stream 2>/dev/null"; + video_proc = bp::child("/bin/bash", bp::args = {"-c", commnd}); +} + +// 确认是否已经进行过认证 +bool ConfirmInit() +{ + ReadFile *rf = new ReadFile(filepath); + if (rf->Open() == false) + { + cerr << "文件打开失败\n"; + return false; + } + + auto vct = rf->ReadLines(); + for (auto &ii : vct) + { + if (ii.find("InitOrNot:no") != string::npos) + return false; + } + return true; +} + +// 获取cpu序列号和SIM卡号 +string GetCardInfo() +{ + ReadFile *rf = new ReadFile("/proc/cpuinfo"); + if (rf->Open() == false) + { + cerr << "文件打开失败\n"; + return ""; + } + + auto lines = rf->ReadLines(); + string res = ""; + for (auto &ii : lines) + { + if (ii.find("Serial") != string::npos) + { + auto pos = ii.find(":"); + if (pos != string::npos) + { + res = ii.substr(pos + 1); + break; + } + } + } + + return LRtrim(res); +} + +// 获取SIM卡号 +// 通过串口发送 AT+CCID 指令,读取并解析返回的ICCID号 +string GetSimICCID(const string &tty) +{ + int retry = 0; // 重试次数 + while (retry < 5) // 最多重试5次 + { + int fd = open(tty.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); // 打开串口 + if (fd < 0) + { + std::cerr << "无法打开串口: " << tty << std::endl; + return ""; + } + + // 配置串口参数 + struct termios options; + tcgetattr(fd, &options); + cfsetispeed(&options, B115200); + cfsetospeed(&options, B115200); + options.c_cflag |= (CLOCAL | CREAD); + tcsetattr(fd, TCSANOW, &options); + + // 发送 AT+CCID 指令 + write(fd, "AT+CCID\r\n", 9); + + std::string result; + char buf[256] = {0}; + // 循环多次读取,拼接内容,防止数据分包 + for (int i = 0; i < 10; ++i) + { + usleep(100000); // 每次等待100ms + int n = read(fd, buf, sizeof(buf) - 1); + if (n > 0) + result.append(buf, n); + } + close(fd); // 关闭串口 + + // 检查是否有ERROR,若有则重试 + if (result.find("ERROR") != std::string::npos) + { + retry++; + usleep(200000); // 等待200ms再重试 + continue; + } + + // 用正则提取ICCID(+CCID: 后面的数字) + std::smatch m; + std::regex reg(R"(\+CCID:\s*([0-9]+))"); + if (std::regex_search(result, m, reg)) + return m[1]; + + // 兜底:直接找19~22位数字(兼容不同长度的ICCID) + std::regex reg2(R"((\d{19,22}))"); + if (std::regex_search(result, m, reg2)) + return m[1]; + + // 没有ERROR但也没读到ICCID,直接退出循环 + break; + } + return ""; // 多次重试失败,返回空 +} + +// 发送cpu序列号和SIM卡号 +void SendCardInfo(mqtt::async_client &client) +{ + string CardID = GetCardInfo(); + string SIMID = GetSimICCID(); + string info = CardID + SIMID; + + // 发送信息 + client.publish(Topic, info, Qos, false); +} + +// 解析接收的数据 +template +void CalculateInfo(T &conf, const string &json) +{ + try + { + /* code */ + auto j = nlohmann::json::parse(json); + if (j.contains("params") && j["params"].contains("media")) + { + auto conf = j["params"]["media"]; + if (conf.contains("mirror")) + media.mirror = conf["mirror"].get(); + if (conf.contains("flip")) + media.flip = conf["mirror"].get(); + if (conf.contains("occlusion")) + media.occlusion = conf["occlusion"].get(); + } + else if (j.contains("params") && j["params"].contains("algorithm")) + { + auto conf = j["params"]["algorithm"]; + if (conf.contains("alarm_output_mode")) + algor.outPutMode = (conf["alarm_output_mode"].get().find("高电平") != string::npos) ? true : false; + + if (conf.contains("trigger_input_mode")) + algor.inPutMode = (conf["trigger_input_mode"].get().find("高电平") != string::npos) ? true : false; + } + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } +} diff --git a/softWareInit/src/makefile b/softWareInit/src/makefile new file mode 100644 index 0000000..7a694b8 --- /dev/null +++ b/softWareInit/src/makefile @@ -0,0 +1,8 @@ +all:verification + +verification:main.cpp + g++ -g -o verification main.cpp /home/orangepi/RKApp/softWareInit/NetraLib/src/Netra.cpp /home/orangepi/RKApp/softWareInit/NetraLib/src/encrypt.cpp /home/orangepi/RKApp/softWareInit/NetraLib/src/NetRequest.cpp -I/home/orangepi/RKApp/softWareInit/NetraLib/include -lpaho-mqttpp3 -lpaho-mqtt3a -lpthread + mv ./verification ../bin/verification + +clean: + rm -rf ../bin/verification \ No newline at end of file