Tinger 1 年之前
当前提交
71fe0677e9
共有 29 个文件被更改,包括 119115 次插入0 次删除
  1. 1 0
      .gitignore
  2. 8 0
      .idea/.gitignore
  3. 2 0
      .idea/IntellegentGate.iml
  4. 18 0
      .idea/deployment.xml
  5. 7 0
      .idea/encodings.xml
  6. 4 0
      .idea/misc.xml
  7. 8 0
      .idea/modules.xml
  8. 10 0
      .idea/other.xml
  9. 6 0
      .idea/vcs.xml
  10. 17 0
      CMakeLists.txt
  11. 371 0
      backup.txt
  12. 12 0
      config.ini
  13. 725 0
      inc/device.cpp
  14. 100 0
      inc/device.h
  15. 116262 0
      inc/dhnetsdk.h
  16. 448 0
      inc/http.cpp
  17. 12 0
      inc/http.h
  18. 29 0
      inc/items.cpp
  19. 22 0
      inc/items.h
  20. 166 0
      inc/json/parser.cpp
  21. 37 0
      inc/json/parser.h
  22. 185 0
      inc/json/value.cpp
  23. 104 0
      inc/json/value.h
  24. 223 0
      inc/manager.cpp
  25. 37 0
      inc/manager.h
  26. 220 0
      inc/utils.cpp
  27. 58 0
      inc/utils.h
  28. 二进制
      lib/libdhnetsdk.so
  29. 23 0
      main.cpp

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+cmake-build-debug

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 2 - 0
.idea/IntellegentGate.iml

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module classpath="CMake" type="CPP_MODULE" version="4" />

+ 18 - 0
.idea/deployment.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
+    <serverData>
+      <paths name="vm (ae6a280b-6cad-436c-b8aa-e85baf91c5d0)">
+        <serverdata>
+          <mappings>
+            <mapping deploy="/tmp/IntelligentGate" local="$PROJECT_DIR$" />
+          </mappings>
+          <excludedPaths>
+            <excludedPath local="true" path="$PROJECT_DIR$/cmake-build-debug" />
+            <excludedPath local="true" path="$PROJECT_DIR$/cmake-build-debug" />
+          </excludedPaths>
+        </serverdata>
+      </paths>
+    </serverData>
+  </component>
+</project>

+ 7 - 0
.idea/encodings.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/inc/dhnetsdk.h" charset="UTF-8" />
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>

+ 4 - 0
.idea/misc.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/IntellegentGate.iml" filepath="$PROJECT_DIR$/.idea/IntellegentGate.iml" />
+    </modules>
+  </component>
+</project>

+ 10 - 0
.idea/other.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="AutoUploadManager">
+    <option name="hosts">
+      <list>
+        <option value="ae6a280b-6cad-436c-b8aa-e85baf91c5d0" />
+      </list>
+    </option>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 17 - 0
CMakeLists.txt

@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.16)
+project(IntellegentGate)
+
+set(CMAKE_CXX_STANDARD 17)
+
+include_directories(inc)
+
+link_directories(lib)
+
+find_package(CURL REQUIRED)
+
+add_executable(IntellegentGate main.cpp
+        inc/http.cpp inc/utils.cpp inc/manager.cpp inc/device.cpp inc/items.cpp
+        inc/json/parser.cpp inc/json/value.cpp
+)
+
+target_link_libraries(IntellegentGate dhnetsdk curl -lpthread -lm)

+ 371 - 0
backup.txt

@@ -0,0 +1,371 @@
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <fstream>
+#include <unistd.h>
+#include "dhnetsdk.h"
+
+#define Log(x...) printf("[HmGate|%3d] %14s : ",__LINE__,__FUNCTION__),printf(x),printf("\n")
+
+int imageRead(const std::string &path, char *&buffer) {
+    std::ifstream img(path, std::ios::binary);
+    img.seekg(0, std::ios::end);
+    int size = (int) img.tellg();
+    img.seekg(0, std::ios::beg);
+    buffer = new char[size];
+    img.read(buffer, size);
+    return size;
+}
+
+void imageSave(const std::string &path, char *&start, const int &size) {
+    if (size > 0 && start != nullptr) {
+        std::ofstream out(path, std::ios::binary);
+        if (!out.is_open()) return;
+        out.write(start, size);
+        out.close();
+    }
+}
+
+class Device {
+private:
+    LLONG loginId;
+    const char *uid = "TempUserId", *name = "临时用户", *path = "./face.jpg";
+
+    // 用户信息查询:CLIENT_StartFindUserInfo
+    void userFind() {
+        NET_IN_USERINFO_START_FIND stin;
+        stin.dwSize = sizeof stin;
+        strcpy(stin.szUserID, "");
+        NET_OUT_USERINFO_START_FIND stout;
+        stout.dwSize = sizeof stout;
+
+        LLONG handler = CLIENT_StartFindUserInfo(loginId, &stin, &stout, 1000);
+        if (handler) {
+            Log("start: cap: %d, total: %d", stout.nCapNum, stout.nTotalCount);
+            NET_IN_USERINFO_DO_FIND din;
+            din.dwSize = sizeof din;
+            din.nStartNo = 0;
+            din.nCount = 3;
+            NET_OUT_USERINFO_DO_FIND dout;
+            dout.dwSize = sizeof dout;
+            dout.nMaxNum = 3;
+            dout.pstuInfo = new NET_ACCESS_USER_INFO[3];
+
+            bool res = CLIENT_DoFindUserInfo(handler, &din, &dout, 1000);
+            if (res) {
+                Log("find success: count: %d", dout.nRetNum);
+                for (int i = 0; i < dout.nRetNum; ++i) Log("user[%d].name: %s", i + 1, dout.pstuInfo[i].szName);
+            } else {
+                Log("find fail: %x", CLIENT_GetLastError());
+            }
+            delete[] dout.pstuInfo;
+        } else {
+            Log("can't start");
+        }
+        CLIENT_StopFindUserInfo(handler);
+        sleep(1);
+    }
+
+    void faceAdd() {
+        Log("-");
+        NET_IN_ADD_FACE_INFO in;
+        in.dwSize = sizeof in;
+        strcpy(in.szUserID, uid);
+        in.stuFaceInfo.nFacePhoto = 1;
+        char *img;
+        in.stuFaceInfo.nFacePhotoLen[0] = imageRead(path, img);
+        in.stuFaceInfo.pszFacePhoto[0] = (char *) img;
+
+        NET_OUT_ADD_FACE_INFO out;
+        out.dwSize = sizeof out;
+        bool res = CLIENT_FaceInfoOpreate(loginId, EM_FACEINFO_OPREATE_ADD, &in, &out, 1000);
+        if (res) {
+            Log("add face success");
+        } else {
+            Log("add face fail: %x", CLIENT_GetLastError());
+        }
+        delete[] img;
+    }
+
+    void userAdd() {
+        Log("-");
+        NET_IN_ACCESS_USER_SERVICE_INSERT in;
+        in.dwSize = sizeof in;
+        in.nInfoNum = 1;
+        auto *user = new NET_ACCESS_USER_INFO;
+        user->bUserIDEx = 0;
+        strcpy(user->szUserID, uid);
+        user->bUseNameEx = 0;
+        strcpy(user->szName, name);
+        user->emUserType = NET_ENUM_USER_TYPE_NORMAL;
+        user->nUserStatus = 0;
+        user->nDoorNum = 1, user->nDoors[0] = 0;
+        user->nTimeSectionNum = 1, user->nTimeSectionNo[0] = 255;
+        user->stuValidEndTime.dwYear = 2100, user->stuValidEndTime.dwMonth = 1, user->stuValidEndTime.dwDay = 1,
+        user->stuValidEndTime.dwHour = 0, user->stuValidEndTime.dwMinute = 0, user->stuValidEndTime.dwSecond = 0;
+        user->emAuthority = NET_ATTENDANCE_AUTHORITY_CUSTOMER;
+        in.pUserInfo = user;
+
+        NET_OUT_ACCESS_USER_SERVICE_INSERT out;
+        out.dwSize = sizeof out;
+        out.nMaxRetNum = 1;
+        out.pFailCode = new NET_EM_FAILCODE;
+        bool res = CLIENT_OperateAccessUserService(loginId, NET_EM_ACCESS_CTL_USER_SERVICE_INSERT, &in, &out, 3000);
+        if (res) {
+            Log("add success: %d", out.nMaxRetNum);
+            faceAdd();
+        } else {
+            Log("query fail: %x, code: %d", CLIENT_GetLastError(), *out.pFailCode);
+        }
+        delete user;
+        delete out.pFailCode;
+    }
+
+    void faceRemove() {
+        Log("-");
+        NET_IN_REMOVE_FACE_INFO in;
+        in.dwSize = sizeof in;
+        strcpy(in.szUserID, uid);
+
+        NET_OUT_REMOVE_FACE_INFO out;
+        out.dwSize = sizeof out;
+        bool res = CLIENT_FaceInfoOpreate(loginId, EM_FACEINFO_OPREATE_REMOVE, &in, &out, 3000);
+        if (res) {
+            Log("delete face success");
+        } else {
+            Log("delete face fail: %x", CLIENT_GetLastError());
+        }
+    }
+
+    void userRemove() {
+        faceRemove();
+        Log("-");
+        NET_IN_ACCESS_USER_SERVICE_REMOVE in;
+        in.dwSize = sizeof in;
+        in.nUserNum = 1;
+        std::strcpy(in.szUserID[0], uid);
+        in.bUserIDEx = 0;
+        NET_OUT_ACCESS_USER_SERVICE_REMOVE out;
+        out.dwSize = sizeof out;
+        out.nMaxRetNum = 1;
+        out.pFailCode = new NET_EM_FAILCODE;
+
+        bool res = CLIENT_OperateAccessUserService(loginId, NET_EM_ACCESS_CTL_USER_SERVICE_REMOVE, &in, &out, 1000);
+        if (res) {
+            Log("delete user success");
+        } else {
+            Log("delete user fail: %x", CLIENT_GetLastError());
+        }
+        delete out.pFailCode;
+    }
+
+    void carQuery(const bool &isBlack) {
+        Log("-");
+        NET_IN_FIND_RECORD_PARAM find_in;
+        find_in.dwSize = sizeof find_in;
+        find_in.emType = isBlack ? NET_RECORD_TRAFFICBLACKLIST : NET_RECORD_TRAFFICREDLIST;
+        auto *find_cond = new FIND_RECORD_TRAFFICREDLIST_CONDITION;
+        find_cond->dwSize = sizeof(FIND_RECORD_TRAFFICREDLIST_CONDITION);
+        strcpy(find_cond->szPlateNumber, "");
+        strcpy(find_cond->szPlateNumberVague, "");
+        find_cond->nQueryResultBegin = 0;
+        find_cond->bRapidQuery = 0;
+        find_in.pQueryCondition = find_cond;
+
+        NET_OUT_FIND_RECORD_PARAM find_out;
+        find_out.dwSize = sizeof find_out;
+
+        bool res = CLIENT_FindRecord(loginId, &find_in, &find_out);
+        if (res) {
+            Log("find success: handler = %ld", find_out.lFindeHandle);
+            NET_IN_QUEYT_RECORD_COUNT_PARAM query_in;
+            query_in.dwSize = sizeof query_in;
+            query_in.lFindeHandle = find_out.lFindeHandle;
+
+            NET_OUT_QUEYT_RECORD_COUNT_PARAM query_out;
+            query_out.dwSize = sizeof query_out;
+
+            res = CLIENT_QueryRecordCount(&query_in, &query_out);
+            if (res) {
+                Log("count success: total = %d", query_out.nRecordCount);
+                int found = 0;
+                while (found < query_out.nRecordCount) {
+                    NET_IN_FIND_NEXT_RECORD_PARAM do_in;
+                    do_in.dwSize = sizeof do_in;
+                    do_in.lFindeHandle = find_out.lFindeHandle;
+                    do_in.nFileCount = 5;
+
+                    NET_OUT_FIND_NEXT_RECORD_PARAM do_out;
+                    do_out.dwSize = sizeof do_out;
+                    do_out.nMaxRecordNum = 5;
+                    auto *records = new NET_TRAFFIC_LIST_RECORD[5];
+                    do_out.pRecordList = records;
+                    for (int i = 0; i < 5; ++i) records[i].dwSize = sizeof(NET_TRAFFIC_LIST_RECORD);
+
+                    res = CLIENT_FindNextRecord(&do_in, &do_out);
+                    if (res) {
+                        // Log("query success: return = %d, max = %d", do_out.nRetRecordNum, do_out.nMaxRecordNum);
+                        found += do_out.nRetRecordNum;
+                        for (int i = 0; i < do_out.nRetRecordNum; ++i) {
+                            Log(
+                                    "id: %d, name: %s, plate: %s, auth-num: %d, control-num: %d, start: %d-%d-%d %d:%d:%d, end: %d-%d-%d %d:%d:%d",
+                                    records[i].nRecordNo, records[i].szMasterOfCar, records[i].szPlateNumber,
+                                    records[i].nAuthrityNum, records[i].emControlType,
+                                    records[i].stBeginTime.dwYear, records[i].stBeginTime.dwMonth,
+                                    records[i].stBeginTime.dwDay, records[i].stBeginTime.dwHour,
+                                    records[i].stBeginTime.dwMinute, records[i].stBeginTime.dwSecond,
+                                    records[i].stCancelTime.dwYear, records[i].stCancelTime.dwMonth,
+                                    records[i].stCancelTime.dwDay, records[i].stCancelTime.dwHour,
+                                    records[i].stCancelTime.dwMinute, records[i].stCancelTime.dwSecond
+                            );
+                        }
+                    } else {
+                        Log("query failed: %u", CLIENT_GetLastError());
+                    }
+                    delete[] records;
+                }
+            } else {
+                Log("count failed: %u", CLIENT_GetLastError());
+            }
+            CLIENT_FindRecordClose(find_out.lFindeHandle);
+        } else {
+            Log("find failed: %u", CLIENT_GetLastError());
+        }
+        delete find_cond;
+    }
+
+    void carAdd(const bool &isBlack) {
+        Log("-");
+        NET_IN_OPERATE_TRAFFIC_LIST_RECORD in;
+        in.dwSize = sizeof in;
+        in.emOperateType = NET_TRAFFIC_LIST_INSERT;
+        in.emRecordType = isBlack ? NET_RECORD_TRAFFICBLACKLIST : NET_RECORD_TRAFFICREDLIST;
+        auto *info = new NET_INSERT_RECORD_INFO;
+        info->dwSize = sizeof(NET_INSERT_RECORD_INFO);
+        auto *record = new NET_TRAFFIC_LIST_RECORD;
+        strcpy(record->szMasterOfCar, name);
+        strcpy(record->szPlateNumber, plate);
+        record->stBeginTime.dwYear = 2000, record->stBeginTime.dwMonth = 1, record->stBeginTime.dwDay = 1,
+        record->stBeginTime.dwHour = 0, record->stBeginTime.dwMinute = 0, record->stBeginTime.dwSecond = 0;
+        record->stCancelTime.dwYear = 2077, record->stCancelTime.dwMonth = 1, record->stCancelTime.dwDay = 1,
+        record->stCancelTime.dwHour = 0, record->stCancelTime.dwMinute = 0, record->stCancelTime.dwSecond = 0;
+        record->nAuthrityNum = 0;
+        info->pRecordInfo = record;
+        in.pstOpreateInfo = info;
+
+        NET_OUT_OPERATE_TRAFFIC_LIST_RECORD out;
+        out.dwSize = sizeof out;
+        bool res = CLIENT_OperateTrafficList(loginId, &in, &out);
+        if (res) {
+            Log("add success: record-number = %d", out.nRecordNo);
+        } else {
+            Log("add failed: %u", CLIENT_GetLastError());
+        }
+        delete record;
+        delete info;
+    }
+
+    void carDel(const bool &isBlack, const int &id) {
+        Log("-");
+        NET_IN_OPERATE_TRAFFIC_LIST_RECORD in;
+        in.dwSize = sizeof in;
+        in.emOperateType = NET_TRAFFIC_LIST_REMOVE;
+        in.emRecordType = isBlack ? NET_RECORD_TRAFFICBLACKLIST : NET_RECORD_TRAFFICREDLIST;
+        auto *info = new NET_REMOVE_RECORD_INFO;
+        info->dwSize = sizeof(NET_REMOVE_RECORD_INFO);
+        info->nRecordNo = id;
+        in.pstOpreateInfo = info;
+
+        NET_OUT_OPERATE_TRAFFIC_LIST_RECORD out;
+        out.dwSize = sizeof out;
+
+        bool res = CLIENT_OperateTrafficList(loginId, &in, &out);
+        if (res) {
+            Log("delete success: id = %d", out.nRecordNo);
+        } else {
+            Log("delete failed: %u", CLIENT_GetLastError());
+        }
+        delete info;
+    }
+
+public:
+    Device() {
+        bool ok = CLIENT_Init(nullptr, 0);
+        CLIENT_SetAutoReconnect(nullptr, 0);
+        CLIENT_SetConnectTime(3000, 3);
+        if (!ok) {
+            Log("SDK init failed.");
+            exit(-1);
+        }
+        Log("SDK init success");
+
+        NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY in = {0};
+        in.dwSize = sizeof in;
+        strcpy(in.szIP, "192.168.1.108");
+        in.nPort = 37777;
+        strcpy(in.szUserName, "admin");
+        strcpy(in.szPassword, "hmkj6688");
+
+        NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY out;
+        out.dwSize = sizeof out;
+
+        this->loginId = CLIENT_LoginWithHighLevelSecurity(&in, &out);
+        if (!this->loginId) {
+            Log("login failed.");
+            exit(-1);
+        }
+        Log("login success");
+    }
+
+    void Run() {
+        userFind();
+//        userAdd();
+//        userFind();
+//        userRemove();
+    }
+
+    ~Device() {
+        Log("clean, Bye ~");
+        CLIENT_Cleanup();
+    }
+};
+
+int main(int count, char **args) {
+    Device device;
+    device.Run();
+    return 0;
+}
+
+
+/// json test
+#include "json/parser.h"
+#include <iostream>
+
+using namespace std;
+int main() {
+    string str1 = R"({
+"name": "Tinger",
+"age": 18,
+"words": ["this", "is", 123, null, false],
+})", str2 = R"([
+{
+    "name": "jack",
+    "age": 26
+}, {
+    "name": "jay",
+    "age": 32
+}, {
+    "name": "john",
+    "age": 62
+}
+])", str3 = R"({
+"int": 2,
+"double": 96.26,
+"string": "casef"
+})";
+    json::Parser parser;
+    json::Value value = parser.parse(str3);
+    cout << value.str() << " " << value["465"].type_name() << endl;
+    return 0;
+}

+ 12 - 0
config.ini

@@ -0,0 +1,12 @@
+# faceRemote = https://huatong.ifarmcloud.com/secureApi/bill-gate/getGateData
+# carRemote  = https://huatong.ifarmcloud.com/secureApi/bill-gate/getDoorData
+faceRemote   = http://192.168.1.6:3080/upload
+carRemote    = http://192.168.1.6:3080/upload
+user         = admin
+pass         = hmkj6688
+logLevel     = 1
+serverPort   = 8040
+
+# 单位:s
+searchGap      = 60
+searchDuration = 4

+ 725 - 0
inc/device.cpp

@@ -0,0 +1,725 @@
+#include <cstring>
+#include <unistd.h>
+#include "utils.h"
+#include "device.h"
+#include "json/parser.h"
+
+// declare of scoped vars and funcs
+namespace hm {
+    const int gWaitTime = 3000;
+    std::map<int, std::string> gOpenTypeMap = {  // NOLINT
+            {0,  "未知方式"},
+            {1,  "密码开锁"},
+            {2,  "刷卡开锁"},
+            {3,  "先刷卡后密码开锁"},
+            {4,  "先密码后刷卡开锁"},
+            {5,  "远程开锁,如通过室内机或者平台对门口机开锁"},
+            {6,  "开锁按钮进行开锁"},
+            {7,  "开锁"},
+            {8,  "密码+刷卡+组合开锁"},
+            {10, "密码+组合开锁"},
+            {11, "刷卡+组合开锁"},
+            {12, "多人开锁"},
+            {13, "钥匙开门"},
+            {14, "胁迫密码开门"},
+            {15, "二维码开门"},
+            {16, "目标识别开门"},
+            {18, "人证对比"},
+            {19, "证件+人证比对"},
+            {20, "蓝牙开门"},
+            {21, "个性化密码开门"},
+            {22, "UserID+密码"},
+            {23, "人脸+密码开锁"},
+            {24, "+密码开锁"},
+            {25, "+人脸开锁"},
+            {26, "刷卡+人脸开锁"},
+            {27, "人脸或密码开锁"},
+            {28, "或密码开锁"},
+            {29, "或人脸开锁"},
+            {30, "刷卡或人脸开锁"},
+            {31, "刷卡或开锁"},
+            {32, "+人脸+密码开锁"},
+            {33, "刷卡+人脸+密码开锁"},
+            {34, "刷卡++密码开锁"},
+            {35, "卡++人脸组合开锁"},
+            {36, "或人脸或密码"},
+            {37, "卡或人脸或密码开锁"},
+            {38, "卡或人脸开锁"},
+            {39, "卡++人脸+密码组合开锁"},
+            {40, "卡或人脸或密码开锁"},
+            {41, "(证件+人证比对)或刷卡或人脸"},
+            {42, "人证比对或刷卡(二维码)或人脸"},
+            {43, "DTMF开锁(包括SIPINFO,RFC2833,INBAND)"},
+            {44, "远程二维码开门"},
+            {45, "远程人脸开门"},
+            {46, "人证比对开门()"},
+            {47, "临时密码开门"},
+            {48, "健康码开门"},
+            {49, "目标识别开锁"},
+            {50, "目标+密码组合开锁"},
+            {51, "人脸+目标组合开锁"},
+            {52, "卡+目标组合开锁"},
+            {53, "目标或密码开锁"},
+            {54, "人脸或目标开锁"},
+            {55, "卡或目标开锁"},
+            {56, "人脸+目标+密码组合开锁"},
+            {57, "卡+人脸+目标组合开锁"},
+            {58, "卡+目标+密码组合开锁"},
+            {59, "人脸或目标或密码开锁"},
+            {60, "卡或人脸或目标开锁"},
+            {61, "卡或目标或密码开锁"},
+            {62, "卡+人脸+目标+密码组合开锁"},
+            {63, "卡或人脸或目标或密码开锁"}
+    };
+
+    int onSubscribeData(
+            LLONG handler, DWORD type, void *info, BYTE *imgBuf, DWORD bufSize,
+            LDWORD user, int seq, void *reserved
+    );
+
+    Device *getDeviceBySubId(const LLONG &subId);
+}
+
+// implement of scoped funcs
+namespace hm {
+    int onSubscribeData(
+            LLONG handler, DWORD type, void *info, BYTE *imgBuf, DWORD bufSize,
+            LDWORD user, int seq, void *reserved
+    ) {
+        Device *device = getDeviceBySubId(handler);
+        if (device == nullptr) {
+            Log(Warn, "unrecognized subscribe uid: %ld", handler);
+            return 0;
+        }
+        switch (type) {
+            case EVENT_IVS_ACCESS_CTL:  // 人脸
+                if (device->isFaceDevice)
+                    device->onAccessCtlEvent((DEV_EVENT_ACCESS_CTL_INFO *) info, imgBuf, bufSize);
+                else Log(Warn, "seq[%s] is car device, but got a face event", device->seq.c_str());
+                break;
+            case EVENT_IVS_TRAFFICJUNCTION:  // 车辆闸机
+                if (!device->isFaceDevice)
+                    device->onTrafficJunctionEvent((DEV_EVENT_TRAFFICJUNCTION_INFO *) info, imgBuf, bufSize);
+                else Log(Warn, "seq[%s] is face device, but got a car event", device->seq.c_str());
+                break;
+            default:
+                Log(Warn, "unrecognized event: %u", type);
+                break;
+        }
+        return 1;
+    }
+
+    Device *getDeviceBySubId(const LLONG &subId) {
+        for (auto &itr : gDevicesVec) if (itr->subId == subId) return itr.get();
+        return nullptr;
+    }
+
+    void Device::init() {
+        NET_IN_INIT_DEVICE_ACCOUNT in;
+        in.dwSize = sizeof in;
+        strcpy(in.szMac, this->mac.c_str());
+        strcpy(in.szUserName, gConfLoginUser.c_str());
+        strcpy(in.szPwd, gConfLoginPass.c_str());
+        in.byPwdResetWay = this->reset;
+
+        NET_OUT_INIT_DEVICE_ACCOUNT out;
+        out.dwSize = sizeof out;
+
+        if (CLIENT_InitDevAccount(&in, &out, gWaitTime, this->local.data())) {
+            Log(Info, "device: seq[%s] init success", this->seq.c_str());
+            this->inited = true;
+            return;
+        }
+        Log(
+                Error, "device(ip[%s], mac[%s], seq[%s]) init fail",
+                this->ip.c_str(), this->mac.c_str(), this->seq.c_str()
+        );
+        this->running = false;
+    }
+
+    bool Device::login() {
+        NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY in = {0};
+        in.dwSize = sizeof in;
+        strcpy(in.szIP, this->ip.c_str());
+        in.nPort = this->port;
+        strcpy(in.szUserName, gConfLoginUser.c_str());
+        strcpy(in.szPassword, gConfLoginPass.c_str());
+        in.emSpecCap = EM_LOGIN_SPEC_CAP_TCP;
+
+        NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY out;
+        out.dwSize = sizeof out;
+
+        for (int i = 0; i < 3; ++i) {
+            this->loginId = CLIENT_LoginWithHighLevelSecurity(&in, &out);
+            if (this->loginId) {
+                Log(Trace, "device seq[%s] login success", this->seq.c_str());
+                usleep(1000000);
+                return true;
+            }
+            sleep(1);
+        }
+        Log(
+                Error, "device(ip[%s], mac[%s], seq[%s]) login fail",
+                this->ip.c_str(), this->mac.c_str(), this->seq.c_str()
+        );
+        this->running = false;
+        return false;
+    }
+
+    void Device::logout() {
+        if (this->loginId) CLIENT_Logout(this->loginId);
+        this->loginId = 0, this->running = false;
+    }
+
+    bool Device::subscribe() {
+        if (isFaceDevice) {
+            this->subId = CLIENT_RealLoadPictureEx(
+                    this->loginId, 0, EVENT_IVS_ACCESS_CTL, TRUE,
+                    onSubscribeData, 0, nullptr
+            );
+        } else {
+            this->subId = CLIENT_RealLoadPictureEx(
+                    this->loginId, 0, EVENT_IVS_TRAFFICJUNCTION, TRUE,
+                    onSubscribeData, 0, nullptr
+            );
+        }
+        if (this->subId) {
+            Log(Trace, "device seq[%s] subscribe success", this->seq.c_str());
+            return true;
+        }
+        Log(
+                Error, "device(ip[%s], mac[%s], seq[%s]) face subscribe fail",
+                this->ip.c_str(), this->mac.c_str(), this->seq.c_str()
+        );
+        this->running = false;
+        return false;
+    }
+
+    void Device::unsubscribe() {
+        if (this->subId) CLIENT_StopLoadPic(this->subId);
+        this->subId = 0, this->running = false;
+    }
+
+    void Device::varUserInsert(const User &user) {
+        bool have = false;
+        std::lock_guard<std::mutex> lock(uMtx);
+        for (const User &itr : faceUserList) {
+            if (itr.uid == user.uid) {
+                have = true;
+                break;
+            }
+        }
+        if (!have) faceUserList.push_back(user);
+    }
+
+    void Device::varUserDelete(const std::string &uid) {
+        std::lock_guard<std::mutex> lock(uMtx);
+        for (auto it = faceUserList.begin(); it != faceUserList.end(); ++it) {
+            if (it->uid == uid) {
+                faceUserList.erase(it);
+                break;
+            }
+        }
+    }
+
+    bool Device::varUserExists(const std::string &uid) {
+        std::lock_guard<std::mutex> lock(uMtx);
+        for (User &user : faceUserList) if (user.uid == uid) return true;
+        return false;
+    }
+
+    bool Device::addUserFace(const std::string &uid, const std::string &face, std::string &msg) {
+        NET_IN_ADD_FACE_INFO in;
+        in.dwSize = sizeof in;
+        strcpy(in.szUserID, uid.c_str());
+        in.stuFaceInfo.nFacePhoto = 1;
+        in.stuFaceInfo.nFacePhotoLen[0] = imgcpy(in.stuFaceInfo.pszFacePhoto[0], face);
+        // Log(Info, "face-size: %ld, pho-len:%d", face.size(), in.stuFaceInfo.nFacePhotoLen[0]);
+        // imageSave("./java.jpg", in.stuFaceInfo.pszFacePhoto[0], in.stuFaceInfo.nFacePhotoLen[0]);
+
+        NET_OUT_ADD_FACE_INFO out;
+        out.dwSize = sizeof out;
+        bool res = CLIENT_FaceInfoOpreate(loginId, EM_FACEINFO_OPREATE_ADD, &in, &out, gWaitTime);
+        if (!res) msg = "user face add failed";
+        delete[] in.stuFaceInfo.pszFacePhoto[0];
+        return res;
+    }
+
+    bool Device::removeUserOnly(const std::string &uid, std::string &msg) {
+        NET_IN_ACCESS_USER_SERVICE_REMOVE in;
+        in.dwSize = sizeof in;
+        in.nUserNum = 1;
+        std::strcpy(in.szUserID[0], uid.c_str());
+        in.bUserIDEx = 0;
+        NET_OUT_ACCESS_USER_SERVICE_REMOVE out;
+        out.dwSize = sizeof out;
+        out.nMaxRetNum = 1;
+        out.pFailCode = new NET_EM_FAILCODE;
+
+        bool res = CLIENT_OperateAccessUserService(
+                loginId, NET_EM_ACCESS_CTL_USER_SERVICE_REMOVE, &in, &out, gWaitTime
+        );
+        if (res) varUserDelete(uid);
+        else msg = "user info delete failed";
+        delete out.pFailCode;
+        return res;
+    }
+
+    bool Device::removeUserFace(const std::string &uid, std::string &msg) {
+        NET_IN_REMOVE_FACE_INFO in;
+        in.dwSize = sizeof in;
+        strcpy(in.szUserID, uid.c_str());
+
+        NET_OUT_REMOVE_FACE_INFO out;
+        out.dwSize = sizeof out;
+        bool res = CLIENT_FaceInfoOpreate(loginId, EM_FACEINFO_OPREATE_REMOVE, &in, &out, gWaitTime);
+        if (!res) msg = "user face remove failed";
+        return res;
+    }
+
+    void Device::varCarWoBInsert(const bool &isBlack, const Plate &plate) {
+        bool have = false;
+        std::lock_guard<std::mutex> lock(isBlack ? cbMtx : cwMtx);
+        for (const Plate &itr : isBlack ? carBlackList : carWhiteList) {
+            if (itr.cid == plate.cid) {
+                have = true;
+                break;
+            }
+        }
+        if (!have) isBlack ? carBlackList.push_back(plate) : carWhiteList.push_back(plate);
+    }
+
+    void Device::varCarWoBDelete(const bool &isBlack, const int &cid) {
+        std::lock_guard<std::mutex> lock(isBlack ? cbMtx : cwMtx);
+        for (auto it = (isBlack ? carBlackList : carWhiteList).begin();
+             it != (isBlack ? carBlackList : carWhiteList).end(); ++it) {
+            if (it->cid == cid) {
+                (isBlack ? carBlackList : carWhiteList).erase(it);
+                break;
+            }
+        }
+    }
+
+    bool Device::varCarWoBExists(const bool &isBlack, const int &cid) {
+        std::lock_guard<std::mutex> lock(isBlack ? cbMtx : cwMtx);
+        for (const Plate &plate : isBlack ? carBlackList : carWhiteList) if (plate.cid == cid) return true;
+        return false;
+    }
+
+    bool Device::varCarWoBExists(const bool &isBlack, const std::string &plate) {
+        std::lock_guard<std::mutex> lock(isBlack ? cbMtx : cwMtx);
+        for (const Plate &itr : isBlack ? carBlackList : carWhiteList) if (itr.plate == plate) return true;
+        return false;
+    }
+
+    bool Device::insertOneCarWoBList(const bool &isBlack, Plate &plateInfo, std::string &msg) {
+        if (varCarWoBExists(isBlack, plateInfo.plate)) {
+            msg = "exist already";
+            return false;
+        }
+        NET_IN_OPERATE_TRAFFIC_LIST_RECORD in;
+        in.dwSize = sizeof in;
+        in.emOperateType = NET_TRAFFIC_LIST_INSERT;
+        in.emRecordType = isBlack ? NET_RECORD_TRAFFICBLACKLIST : NET_RECORD_TRAFFICREDLIST;
+        auto *info = new NET_INSERT_RECORD_INFO;
+        info->dwSize = sizeof(NET_INSERT_RECORD_INFO);
+        auto *record = new NET_TRAFFIC_LIST_RECORD;
+        strcpy(record->szMasterOfCar, plateInfo.name.c_str());
+        strcpy(record->szPlateNumber, plateInfo.plate.c_str());
+        time_t tst = plateInfo.timestamp / 1000;
+        struct tm *timeInfo = localtime(&tst);
+        record->stCancelTime.dwYear = timeInfo->tm_year + 1900, record->stCancelTime.dwMonth = timeInfo->tm_mon + 1;
+        record->stCancelTime.dwDay = timeInfo->tm_mday, record->stCancelTime.dwHour = timeInfo->tm_hour;
+        record->stCancelTime.dwMinute = timeInfo->tm_min, record->stCancelTime.dwSecond = timeInfo->tm_sec;
+        record->stBeginTime.dwYear = 2000, record->stBeginTime.dwMonth = 1, record->stBeginTime.dwDay = 1;
+        record->stBeginTime.dwHour = 0, record->stBeginTime.dwMinute = 0, record->stBeginTime.dwSecond = 0;
+        record->nAuthrityNum = 0;
+        info->pRecordInfo = record;
+        in.pstOpreateInfo = info;
+
+        NET_OUT_OPERATE_TRAFFIC_LIST_RECORD out;
+        out.dwSize = sizeof out;
+        bool res = CLIENT_OperateTrafficList(loginId, &in, &out);
+        if (res) {
+            plateInfo.cid = out.nRecordNo;
+            plateInfo.cid_s = std::to_string(out.nRecordNo);
+            varCarWoBInsert(isBlack, plateInfo);
+        } else {
+            msg = "insert failed";
+            plateInfo.cid = 0;
+        }
+        delete record;
+        delete info;
+        return res;
+    }
+
+    bool Device::removeOneCarWoBList(const bool &isBlack, const int &cid, std::string &msg) {
+        if (!varCarWoBExists(isBlack, cid)) {
+            msg = "no such plate";
+            return false;
+        }
+        NET_IN_OPERATE_TRAFFIC_LIST_RECORD in;
+        in.dwSize = sizeof in;
+        in.emOperateType = NET_TRAFFIC_LIST_REMOVE;
+        in.emRecordType = isBlack ? NET_RECORD_TRAFFICBLACKLIST : NET_RECORD_TRAFFICREDLIST;
+        auto *info = new NET_REMOVE_RECORD_INFO;
+        info->dwSize = sizeof(NET_REMOVE_RECORD_INFO);
+        info->nRecordNo = cid;
+        in.pstOpreateInfo = info;
+
+        NET_OUT_OPERATE_TRAFFIC_LIST_RECORD out;
+        out.dwSize = sizeof out;
+
+        bool res = CLIENT_OperateTrafficList(loginId, &in, &out);
+        if (res) varCarWoBDelete(isBlack, cid);
+        else msg = "remove failed";
+        delete info;
+        return res;
+    }
+
+    Plate *Device::getPlateByCNo(const std::string &cno) {
+        for (auto &itr : carWhiteList) if (itr.plate == cno) return &itr;
+        for (auto &itr : carBlackList) if (itr.plate == cno) return &itr;
+        return nullptr;
+    }
+}
+
+namespace hm {
+    Device::Device(DEVICE_NET_INFO_EX2 *info) {
+        this->inited = (info->stuDevInfo.byInitStatus & 3) != 1;
+        this->port = info->stuDevInfo.nPort;
+        this->reset = info->stuDevInfo.byPwdResetWay;
+        this->seq = info->stuDevInfo.szSerialNo;
+        this->mac = info->stuDevInfo.szMac;
+        this->ip = info->stuDevInfo.szIP;
+        this->local = info->szLocalIP;
+        // 108: {szDeviceType: BSC, szDetailType: DH-ASI31A-MW, szNewDetailType: DH-ASI31A-MW}
+        // 109: {szDeviceType: ITC, szDetailType: ITC313-PW2A-LF, szNewDetailType: ITC313-PW2A-LF}
+        this->isFaceDevice = strcmp("DH-ASI31A-MW", info->stuDevInfo.szDetailType) == 0;
+
+        this->online = this->running = false;
+        this->loginId = this->subId = 0;
+    }
+
+    Device::~Device() {
+        this->unsubscribe();
+        this->logout();
+    }
+
+    void Device::queryFaceUsers() {
+        NET_IN_USERINFO_START_FIND start_in;
+        start_in.dwSize = sizeof start_in;
+        strcpy(start_in.szUserID, "");
+        NET_OUT_USERINFO_START_FIND start_out;
+        start_out.dwSize = sizeof start_out;
+
+        LLONG handler = CLIENT_StartFindUserInfo(loginId, &start_in, &start_out, gWaitTime);
+        if (handler) {
+            int offset = 0, count = 10;
+            faceUserList.clear();
+            while (offset < start_out.nTotalCount) {
+                NET_IN_USERINFO_DO_FIND find_in;
+                find_in.dwSize = sizeof find_in;
+                find_in.nStartNo = offset;
+                find_in.nCount = count;
+                NET_OUT_USERINFO_DO_FIND find_out;
+                find_out.dwSize = sizeof find_out;
+                find_out.nMaxNum = count;
+                find_out.pstuInfo = new NET_ACCESS_USER_INFO[count];
+                bool res = CLIENT_DoFindUserInfo(handler, &find_in, &find_out, gWaitTime);
+                if (res) {
+                    for (int i = 0; i < find_out.nRetNum; ++i) {
+                        User one = {
+                                .uid=find_out.pstuInfo[i].szUserID, .name=find_out.pstuInfo[i].szName,
+                                .password = find_out.pstuInfo[i].szPsw,
+                                .timestamp=1000 * toTimestamp(
+                                        find_out.pstuInfo[i].stuValidEndTime.dwYear,
+                                        find_out.pstuInfo[i].stuValidEndTime.dwMonth,
+                                        find_out.pstuInfo[i].stuValidEndTime.dwDay,
+                                        find_out.pstuInfo[i].stuValidEndTime.dwHour,
+                                        find_out.pstuInfo[i].stuValidEndTime.dwMinute,
+                                        find_out.pstuInfo[i].stuValidEndTime.dwSecond
+                                )
+                        };
+                        varUserInsert(one);
+                    }
+                    offset += find_out.nRetNum;
+                    delete[] find_out.pstuInfo;
+                } else break;
+            }
+            CLIENT_StopFindUserInfo(handler);
+        }
+    }
+
+    void Device::queryCarWoBList(const bool &isBlack) {
+        NET_IN_FIND_RECORD_PARAM find_in;
+        find_in.dwSize = sizeof find_in;
+        find_in.emType = isBlack ? NET_RECORD_TRAFFICBLACKLIST : NET_RECORD_TRAFFICREDLIST;
+        auto *find_cond = new FIND_RECORD_TRAFFICREDLIST_CONDITION;
+        find_cond->dwSize = sizeof(FIND_RECORD_TRAFFICREDLIST_CONDITION);
+        strcpy(find_cond->szPlateNumber, "");
+        strcpy(find_cond->szPlateNumberVague, "");
+        find_cond->nQueryResultBegin = 0;
+        find_cond->bRapidQuery = 0;
+        find_in.pQueryCondition = find_cond;
+        NET_OUT_FIND_RECORD_PARAM find_out;
+        find_out.dwSize = sizeof find_out;
+        bool res = CLIENT_FindRecord(loginId, &find_in, &find_out);
+        if (res) {
+            NET_IN_QUEYT_RECORD_COUNT_PARAM query_in;
+            query_in.dwSize = sizeof query_in;
+            query_in.lFindeHandle = find_out.lFindeHandle;
+            NET_OUT_QUEYT_RECORD_COUNT_PARAM query_out;
+            query_out.dwSize = sizeof query_out;
+            res = CLIENT_QueryRecordCount(&query_in, &query_out);
+            if (res) {
+                int found = 0, batch = 5;
+                while (found < query_out.nRecordCount) {
+                    NET_IN_FIND_NEXT_RECORD_PARAM do_in;
+                    do_in.dwSize = sizeof do_in;
+                    do_in.lFindeHandle = find_out.lFindeHandle;
+                    do_in.nFileCount = batch;
+                    NET_OUT_FIND_NEXT_RECORD_PARAM do_out;
+                    do_out.dwSize = sizeof do_out;
+                    do_out.nMaxRecordNum = batch;
+                    auto *records = new NET_TRAFFIC_LIST_RECORD[5];
+                    do_out.pRecordList = records;
+                    for (int i = 0; i < batch; ++i) records[i].dwSize = sizeof(NET_TRAFFIC_LIST_RECORD);
+                    res = CLIENT_FindNextRecord(&do_in, &do_out);
+                    if (res) {
+                        found += do_out.nRetRecordNum;
+                        for (int i = 0; i < do_out.nRetRecordNum; ++i) {
+                            Plate one = {
+                                    .name = records[i].szMasterOfCar, .plate = records[i].szPlateNumber,
+                                    .cid_s = std::to_string(records[i].nRecordNo), .cid = records[i].nRecordNo,
+                                    .timestamp = 1000 * toTimestamp(
+                                            records[i].stCancelTime.dwYear, records[i].stCancelTime.dwMonth,
+                                            records[i].stCancelTime.dwDay, records[i].stCancelTime.dwHour,
+                                            records[i].stCancelTime.dwMinute, records[i].stCancelTime.dwSecond
+                                    )
+                            };
+                            varCarWoBInsert(isBlack, one);
+                        }
+                    } else break;
+                    delete[] records;
+                }
+            }
+            CLIENT_FindRecordClose(find_out.lFindeHandle);
+        }
+        delete find_cond;
+    }
+
+    void Device::Start() {
+        if (this->running) return;
+        this->running = true;
+
+        if (!this->inited) this->init();
+        if (!this->login()) return;
+        this->online = true;
+        if (!this->subscribe()) return;
+    }
+
+    void Device::onAccessCtlEvent(DEV_EVENT_ACCESS_CTL_INFO *info, BYTE *imgBuf, DWORD bufSize) {
+        char status[2] = {info->bStatus ? '1' : '0', 0}, method[64] = {0}, time[32] = {0};
+        strcpy(method, gOpenTypeMap[0].c_str());
+        if (gOpenTypeMap.find(info->emOpenMethod) != gOpenTypeMap.end())
+            strcpy(method, gOpenTypeMap[info->emOpenMethod].c_str());
+        sprintf(
+                time, "%d-%02d-%02d %02d:%02d:%02d", info->UTC.dwYear, info->UTC.dwMonth,
+                info->UTC.dwDay, info->UTC.dwHour + 8, info->UTC.dwMinute, info->UTC.dwSecond
+        );
+        Log(
+                Info,
+                R"({"mac":"%s","seq":"%s","time":"%s","result":%s,"userId":"%s","userName":"%s","method":"%s"})",
+                this->mac.c_str(), this->seq.c_str(), time, status, info->szUserID, info->szCardName, method
+        );
+        // imageSave("./face.jpg", imgBuf, bufSize);
+        std::map<std::string, std::string> data{
+                {"mac",      this->mac},
+                {"seq",      this->seq},
+                {"time",     time},
+                {"result",   status},
+                {"userId",   info->szUserID},
+                {"userName", info->szCardName},
+                {"method",   method}
+        };
+        dataUpload(gConfFaceRemote, data, imgBuf, bufSize);
+    }
+
+    void Device::onTrafficJunctionEvent(DEV_EVENT_TRAFFICJUNCTION_INFO *info, BYTE *imgBuf, DWORD bufSize) {
+        char time[32] = {0}, speed[4] = {0}, status[2] = {info->byOpenStrobeState > 1 ? '1' : '0', 0};
+        sprintf(speed, "%d", info->nSpeed);
+        sprintf(
+                time, "%d-%02d-%02d %02d:%02d:%02d", info->UTC.dwYear, info->UTC.dwMonth,
+                info->UTC.dwDay, info->UTC.dwHour, info->UTC.dwMinute, info->UTC.dwSecond
+        );
+        std::string cno(info->stTrafficCar.szPlateNumber), username, cid;
+        if (!cno.empty()) {
+            Plate *plate = getPlateByCNo(cno);
+            if (plate != nullptr) {
+                username = plate->name;
+                cid = plate->cid_s;
+            }
+        }
+        Log(
+                Info,
+                R"({"mac":"%s","seq":"%s","time":"%s","plateNumber":"%s","username":"%s","cid":"%s","plateType":"%s","plateColor":"%s","carType":"%s","carSubType":"%s","carColor":"%s","speed":"%s","result":"%s"})",
+                this->mac.c_str(), this->seq.c_str(), time, info->stTrafficCar.szPlateNumber,
+                username.c_str(), cid.c_str(), info->stTrafficCar.szPlateType, info->stTrafficCar.szPlateColor,
+                info->stuVehicle.szText, info->stuVehicle.szObjectSubType, info->stTrafficCar.szVehicleColor,
+                speed, status
+        );
+
+        // imageSave("./car.jpg", imgBuf, bufSize);
+        std::map<std::string, std::string> data{
+                {"mac",         this->mac},
+                {"seq",         this->seq},
+                {"time",        time},
+                {"plateNumber", info->stTrafficCar.szPlateNumber},
+                {"username",    username},
+                {"cid",         cid},
+                {"plateType",   info->stTrafficCar.szPlateType},
+                {"plateColor",  info->stTrafficCar.szPlateColor},
+                {"carType",     info->stuVehicle.szText},
+                {"carSubType",  info->stuVehicle.szObjectSubType},
+                {"carColor",    info->stTrafficCar.szVehicleColor},
+                {"speed",       speed},
+                {"result",      status},
+        };
+        dataUpload(gConfCarRemote, data, imgBuf, bufSize);
+    }
+
+    std::string Device::getJsonStr() {
+        /* 1: online: 1 -> true, 0 -> false
+         * 37: {"mac":"","seq":"","ip":"","online":}
+         * */
+        size_t size = mac.size() + seq.size() + ip.size() + 38;
+        std::string res(size, 0);
+        sprintf(
+                res.data(), R"({"mac":"%s","seq":"%s","ip":"%s","online":%d})",
+                mac.c_str(), seq.c_str(), ip.c_str(), online
+        );
+        return res;
+    }
+
+    bool Device::getFaceUsers(std::vector<std::string> &users, std::string &msg) {
+        for (User &user : faceUserList) users.push_back(user.getJsonStr());
+        return true;
+    }
+
+    bool Device::insertFaceUser(const User &user, std::string &msg) {
+        if (varUserExists(user.uid)) {
+            msg = "user existed already";
+            return false;
+        }
+        NET_IN_ACCESS_USER_SERVICE_INSERT in;
+        in.dwSize = sizeof in;
+        in.nInfoNum = 1;
+        auto *usr = new NET_ACCESS_USER_INFO;
+        usr->bUserIDEx = 0;
+        strcpy(usr->szUserID, user.uid.c_str());
+        usr->bUseNameEx = 0;
+        strcpy(usr->szName, user.name.c_str());
+        strcpy(usr->szPsw, user.password.c_str());
+        usr->emUserType = NET_ENUM_USER_TYPE_NORMAL;
+        usr->nUserStatus = 0;
+        usr->nDoorNum = 1, usr->nDoors[0] = 0;
+        usr->nTimeSectionNum = 1, usr->nTimeSectionNo[0] = 255;
+        usr->nSpecialDaysScheduleNum = 1, usr->nSpecialDaysSchedule[0] = 255;
+        time_t tst = user.timestamp / 1000;
+        struct tm *timeInfo = localtime(&tst);
+        usr->stuValidEndTime.dwYear = timeInfo->tm_year + 1900;
+        usr->stuValidEndTime.dwMonth = timeInfo->tm_mon + 1;
+        usr->stuValidEndTime.dwDay = timeInfo->tm_mday;
+        usr->stuValidEndTime.dwHour = timeInfo->tm_hour;
+        usr->stuValidEndTime.dwMinute = timeInfo->tm_min;
+        usr->stuValidEndTime.dwSecond = timeInfo->tm_sec;
+        usr->stuValidBeginTime.dwYear = 2000;
+        usr->stuValidBeginTime.dwMonth = 1;
+        usr->stuValidBeginTime.dwDay = 1;
+        usr->stuValidBeginTime.dwHour = 0;
+        usr->stuValidBeginTime.dwMinute = 0;
+        usr->stuValidBeginTime.dwSecond = 0;
+        usr->nFirstEnterDoorsNum = 0;
+        usr->emAuthority = NET_ATTENDANCE_AUTHORITY_CUSTOMER;
+        usr->nFloorNum = 0;
+        usr->nRoom = 0;
+        usr->bFloorNoExValid = 0;
+        usr->nFloorNumEx = 0;
+        usr->bFloorNoEx2Valid = 0;
+        usr->nUserTimeSectionsNum = 0;
+        usr->bUserInfoExValid = 0;
+        usr->bUserInfoEx2Valid = 0;
+        in.pUserInfo = usr;
+
+        NET_OUT_ACCESS_USER_SERVICE_INSERT out;
+        out.dwSize = sizeof out;
+        out.nMaxRetNum = 1;
+        out.pFailCode = new NET_EM_FAILCODE;
+
+        bool ok = true, res = CLIENT_OperateAccessUserService(
+                loginId, NET_EM_ACCESS_CTL_USER_SERVICE_INSERT, &in, &out, gWaitTime
+        );
+        if (res) {
+            res = addUserFace(user.uid, user.face, msg);
+            if (res) varUserInsert(user);
+            else {
+                ok = false;
+                removeUserOnly(user.uid, msg);
+            }
+        } else {
+            ok = false;
+            msg = "usr info insert failed";
+        }
+        delete usr;
+        delete out.pFailCode;
+        return ok;
+    }
+
+    bool Device::removeFaceUser(const std::string &uid, std::string &msg) {
+        if (!varUserExists(uid)) {
+            msg = "no such user";
+            return false;
+        }
+        bool ok1 = removeUserFace(uid, msg), ok2 = removeUserOnly(uid, msg);
+        return ok1 && ok2;
+    }
+
+    bool Device::getCarWoBList(const bool &isBlack, std::vector<std::string> &list, std::string &msg) {
+        for (Plate &plate : isBlack ? carBlackList : carWhiteList) list.push_back(plate.getJsonStr());
+        return true;
+    }
+
+    bool Device::insertCarWoBList(const bool &isBlack, std::vector<Plate> &list, std::string &msg) {
+        json::Parser parser;
+        json::Value result = parser.parse("[]");
+        for (Plate &info : list) {
+            json::Value one = parser.parse("{}");
+            std::string reason;
+            bool res = insertOneCarWoBList(isBlack, info, reason);
+            one["cid"] = json::Value(info.cid);
+            one["plate"] = json::Value(info.plate);
+            one["success"] = json::Value(res);
+            one["msg"] = json::Value(reason);
+            result.push_back(one);
+        }
+        msg = result.str();
+        return true;
+    }
+
+    bool Device::removeCarWoBList(const bool &isBlack, const std::vector<int> &cids, std::string &msg) {
+        json::Parser parser;
+        json::Value result = parser.parse("[]");
+        for (const int &cid : cids) {
+            json::Value one = parser.parse("{}");
+            std::string reason;
+            bool res = removeOneCarWoBList(isBlack, cid, reason);
+            one["cid"] = json::Value(cid);
+            one["success"] = json::Value(res);
+            one["msg"] = json::Value(reason);
+            result.push_back(one);
+        }
+        msg = result.str();
+        return true;
+    }
+}

+ 100 - 0
inc/device.h

@@ -0,0 +1,100 @@
+#ifndef HM_DEVICE_H
+#define HM_DEVICE_H
+
+#include <string>
+#include <mutex>
+#include <memory>
+#include "items.h"
+#include "dhnetsdk.h"
+
+namespace hm {
+    class Device {
+    private:
+        std::mutex uMtx, cwMtx, cbMtx;
+        bool inited, running;
+        int port, reset;
+        std::string mac, ip, local;
+        std::vector<User> faceUserList;
+        std::vector<Plate> carWhiteList, carBlackList;
+
+        void init();
+
+        bool login();
+
+        void logout();
+
+        bool subscribe();
+
+        void unsubscribe();
+
+        void varUserInsert(const User &user);
+
+        void varUserDelete(const std::string &uid);
+
+        bool varUserExists(const std::string &uid);
+
+        bool addUserFace(const std::string &uid, const std::string &face, std::string &msg);
+
+        bool removeUserOnly(const std::string &uid, std::string &msg);
+
+        bool removeUserFace(const std::string &uid, std::string &msg);
+
+        void varCarWoBInsert(const bool &isBlack, const Plate &plate);
+
+        void varCarWoBDelete(const bool &isBlack, const int &cid);
+
+        bool varCarWoBExists(const bool &isBlack, const int &cid);
+
+        bool varCarWoBExists(const bool &isBlack, const std::string &plate);
+
+        bool insertOneCarWoBList(const bool &isBlack, Plate &plateInfo, std::string &msg);
+
+        bool removeOneCarWoBList(const bool &isBlack, const int &cid, std::string &msg);
+
+        Plate *getPlateByCNo(const std::string &cno);
+
+    public:
+        LLONG loginId, subId;
+        bool online, isFaceDevice;
+        std::string seq;
+
+        explicit Device(DEVICE_NET_INFO_EX2 *info);
+
+        Device(const Device &) = delete;
+
+        Device(Device &&) = delete;
+
+        ~Device();
+
+        void queryFaceUsers();
+
+        void queryCarWoBList(const bool &isBlack);
+
+        void Start();
+
+        void onAccessCtlEvent(DEV_EVENT_ACCESS_CTL_INFO *info, BYTE *imgBuf, DWORD bufSize);
+
+        void onTrafficJunctionEvent(DEV_EVENT_TRAFFICJUNCTION_INFO *info, BYTE *imgBuf, DWORD bufSize);
+
+        std::string getJsonStr();
+
+        bool getFaceUsers(std::vector<std::string> &users, std::string &msg);
+
+        bool insertFaceUser(const User &user, std::string &msg);
+
+        bool removeFaceUser(const std::string &uid, std::string &msg);
+
+        bool getCarWoBList(const bool &isBlack, std::vector<std::string> &list, std::string &msg);
+
+        bool insertCarWoBList(const bool &isBlack, std::vector<Plate> &list, std::string &msg);
+
+        bool removeCarWoBList(const bool &isBlack, const std::vector<int> &cids, std::string &msg);
+    };
+
+    // global vars
+    extern std::mutex gDeviceMtx;
+    extern std::vector<std::unique_ptr<Device>> gDevicesVec;
+}
+
+
+#endif //HM_DEVICE_H

文件差异内容过多而无法显示
+ 116262 - 0
inc/dhnetsdk.h


+ 448 - 0
inc/http.cpp

@@ -0,0 +1,448 @@
+#include <string>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <cstring>
+#include <thread>
+
+#include "utils.h"
+#include "json/parser.h"
+#include "manager.h"
+#include "http.h"
+
+// declare of scoped vars and funcs
+namespace hm {
+    int gServerId = 0;
+    std::map<std::string, std::string> _gDefaultHeader = {  // NOLINT
+            {"Origin",                       "HM-Tech CPP"},
+            {"Server",                       "TinWeb"},
+            {"Cross-Origin-Resource-Policy", "*/*"},
+            {"Content-Type",                 "application/json"},
+            {"Content-Length",               "2"},
+    };
+    struct Request {
+        std::string method, uri, ver = "HTTP/1.1", unknown;
+        json::Value json;
+        std::map<std::string, std::string> headers, form, args;
+    };
+
+    struct Response {
+        std::string status, msg, data;
+        std::map<std::string, std::string> headers;
+
+        std::string body_data() const;    // NOLINT
+
+        void response(const int &client) const;
+    };
+
+    Response Success(const std::string &data);
+
+    Response Fail(const std::string &msg);
+
+    std::string vec2str(const std::vector<std::string> &vec);
+
+    void RequestHandler(const int &client);
+
+    Response httpGetFaceDevices(Request &request, const int &client);
+
+    Response httpGetFaceDeviceUsers(Request &request, const int &client);
+
+    Response httpInsertFaceDeviceUser(Request &request, const int &client);
+
+    Response httpRemoveFaceDeviceUser(Request &request, const int &client);
+
+    Response httpGetCarDevices(Request &request, const int &client);
+
+    Response httpGetCarDeviceWoBList(Request &request, const int &client);
+
+    Response httpInsertCarDeviceWoBList(Request &request, const int &client);
+
+    Response httpRemoveCarDeviceWoBList(Request &request, const int &client);
+}
+
+// implement of scoped funcs
+namespace hm {
+    std::string Response::body_data() const {
+        /* 28: {"status":,"msg":"","data":}
+         * */
+        size_t size = status.size() + msg.size() + data.size() + 28;
+        std::string res(size, 0);
+        sprintf(
+                res.data(), R"({"status":%s,"msg":"%s","data":%s})",
+                status.c_str(), msg.c_str(), data.c_str()
+        );
+        return res;
+    }
+
+    void Response::response(const int &client) const {
+        std::string res("HTTP/1.1 200 OK\r\n");
+        for (auto &it : headers) res.append(it.first + ": " + it.second + "\r\n");
+        res.append("\r\n" + body_data());
+        send(client, res.c_str(), res.size(), 0);
+    }
+
+    Response Success(const std::string &data) {
+        Response resp = {.status="true", .msg="success", .data=data, .headers=_gDefaultHeader};
+        resp.headers["Content-Length"] = std::to_string(resp.body_data().size());
+        return resp;
+    }
+
+    Response Fail(const std::string &msg) {
+        Response resp = {.status="false", .msg=msg, .data="null", .headers=_gDefaultHeader};
+        resp.headers["Content-Length"] = std::to_string(resp.body_data().size());
+        return resp;
+    }
+
+    std::string vec2str(const std::vector<std::string> &vec) {
+        std::string res;
+        for (auto &it : vec) res.append(',' + it);
+        if (res.empty()) res = "[]";
+        else res[0] = '[', res.push_back(']');
+        return res;
+    }
+
+    void RequestHandler(const int &client) {
+        size_t size = 1024, read;
+        char charBuf[size];
+        std::string strBuf;
+        read = recv(client, charBuf, size, 0);
+        if (read <= 0) {
+            Fail("Unrecognized Request").response(client);
+            close(client);
+            return;
+        }
+        strBuf.append(charBuf, read);
+        Request request;
+        // request line
+        size_t pos = strBuf.find(' ');
+        request.method = strBuf.substr(0, pos);
+        strBuf.erase(0, pos + 1);
+        pos = strBuf.find(' ');
+        request.uri = strBuf.substr(0, pos);
+        if (request.uri.find('?') != std::string::npos) {
+            request.uri = request.uri.substr(0, request.uri.find('?'));
+            // TODO: parse url args
+        }
+        strBuf.erase(0, pos + 1);
+        pos = strBuf.find("\r\n");
+        request.ver = strBuf.substr(0, pos);
+        strBuf.erase(0, pos + 2);
+        // header
+        pos = strBuf.find("\r\n\r\n");
+        if (pos == std::string::npos) {
+            while (true) {
+                read = recv(client, charBuf, size, 0);
+                strBuf.append(charBuf, read);
+                pos = strBuf.find("\r\n\r\n");
+                if (pos != std::string::npos) break;
+            }
+        }
+        std::string header = strBuf.substr(0, pos + 2), key;
+        strBuf.erase(0, pos + 4);
+        while (true) {
+            pos = header.find(':');
+            if (pos == std::string::npos) break;
+            key = header.substr(0, pos);
+            header.erase(0, pos + 2);
+            pos = header.find('\r');
+            request.headers[key] = header.substr(0, pos);
+            header.erase(0, pos + 2);
+        }
+        // body
+        if (request.headers.find("Content-Length") != request.headers.end()) {
+            size_t rest = stoi(request.headers["Content-Length"]) - strBuf.size();
+            while (rest > 0) {
+                read = recv(client, charBuf, size, 0);
+                if (read <= 0) {
+                    Fail("Error Content Body").response(client);
+                    close(client);
+                    return;
+                }
+                rest -= read;
+                strBuf.append(charBuf, read);
+            }
+        }
+        // form-data only
+        if (request.headers.find("Content-Type") != request.headers.end()) {
+            if (startsWith(request.headers["Content-Type"], "multipart/form-data")) {
+                std::string bond = request.headers["Content-Type"].substr(30);
+                while (true) {
+                    if (strBuf.find(bond) == std::string::npos) break;
+                    strBuf.erase(0, strBuf.find("name=") + 6);
+                    key = strBuf.substr(0, strBuf.find('"'));
+                    strBuf.erase(0, strBuf.find("\r\n\r\n") + 4);
+                    pos = strBuf.find(bond);
+                    request.form[key] = strBuf.substr(0, pos - 4);
+                    strBuf.erase(0, pos + bond.size());
+                }
+                strBuf.clear();
+            } else if (request.headers["Content-Type"] == "application/json") {
+                try {
+                    json::Parser parser;
+                    request.json = parser.parse(strBuf);
+                    strBuf.clear();
+                } catch (std::exception &error) {
+                    Fail(error.what()).response(client);
+                    close(client);
+                    return;
+                }
+            } else {
+                Fail("Unrecognized Content Type").response(client);
+                close(client);
+                return;
+            }
+        } else {
+            if (!strBuf.empty()) {
+                Fail("Content-Type is needed if content is not null").response(client);
+                strBuf.clear();
+                close(client);
+                return;
+            }
+        }
+
+        Response resp;
+        if (request.uri == "/getDevices") resp = httpGetFaceDevices(request, client);
+        else if (request.uri == "/getDeviceUsers") resp = httpGetFaceDeviceUsers(request, client);
+        else if (request.uri == "/addDeviceUser") resp = httpInsertFaceDeviceUser(request, client);
+        else if (request.uri == "/delDeviceUser") resp = httpRemoveFaceDeviceUser(request, client);
+        else if (request.uri == "/getCarDevices") resp = httpGetCarDevices(request, client);
+        else if (request.uri == "/getCarDeviceWoBList") resp = httpGetCarDeviceWoBList(request, client);
+        else if (request.uri == "/addCarDeviceWoBList") resp = httpInsertCarDeviceWoBList(request, client);
+        else if (request.uri == "/delCarDeviceWoBList") resp = httpRemoveCarDeviceWoBList(request, client);
+        else {
+            resp = Fail("API Not Found");
+            resp.response(client);
+        }
+        Log(
+                Info, "method: [%s], uri: [%s], status: %s",
+                request.method.c_str(), request.uri.c_str(), resp.status.c_str()
+        );
+        close(client);
+    }
+
+    Response httpGetFaceDevices(Request &request, const int &client) {
+        Response resp;  // GET/POST
+        std::string msg;
+        std::vector<std::string> devices;
+        if (managerGetFaceDevices(devices, msg)) resp = Success(vec2str(devices));
+        else resp = Fail(msg);
+        resp.response(client);
+        return resp;
+    }
+
+    Response httpGetFaceDeviceUsers(Request &request, const int &client) {
+        Response resp;  // GET/POST
+        if (request.form.find("seq") == request.form.end()) resp = Fail("seq need");
+        else {
+            std::vector<std::string> users;
+            std::string msg;
+            if (managerGetFaceDeviceUsers(request.form["seq"], users, msg)) resp = Success(vec2str(users));
+            else resp = Fail(msg);
+        }
+        resp.response(client);
+        return resp;
+    }
+
+    Response httpInsertFaceDeviceUser(Request &request, const int &client) {
+        Response resp;  // POST
+        if (request.method == "GET") resp = Fail("POST method only");
+        else if (
+                request.form.find("seq") == request.form.end() ||
+                request.form.find("uid") == request.form.end() ||
+                request.form.find("name") == request.form.end() ||
+                request.form.find("password") == request.form.end() ||
+                request.form.find("timestamp") == request.form.end() ||
+                request.form.find("face") == request.form.end()
+                ) {
+            resp = Fail("seq, uid, name, password, timestamp, face[file] need");
+        } else {
+            try {
+                User user = {
+                        .uid = request.form["uid"],
+                        .name = request.form["name"],
+                        .password = request.form["password"],
+                        .face = request.form["face"],
+                        .timestamp = std::stol(request.form["timestamp"])
+                };
+                if (user.face.size() > 200 * 1024) resp = Fail("image is too large");
+                else {
+                    std::string msg;
+                    if (managerInsertFaceDeviceUser(request.form["seq"], user, msg)) {
+                        resp = Success("null");
+                    } else resp = Fail(msg);
+                }
+            } catch (...) {
+                resp = Fail("param type error");
+            }
+        }
+        resp.response(client);
+        return resp;
+    }
+
+    Response httpRemoveFaceDeviceUser(Request &request, const int &client) {
+        Response resp;  // POST
+        if (request.method == "GET") resp = Fail("POST method only");
+        else if (
+                request.form.find("seq") == request.form.end() ||
+                request.form.find("uid") == request.form.end()
+                ) {
+            resp = Fail("seq, uid, name, year, month, day, face[file] need");
+        } else {
+            std::string msg;
+            if (managerRemoveFaceDeviceUser(request.form["seq"], request.form["uid"], msg)) {
+                resp = Success("null");
+            } else resp = Fail(msg);
+        }
+        resp.response(client);
+        return resp;
+    }
+
+    Response httpGetCarDevices(Request &request, const int &client) {
+        Response resp;  // GET/POST
+        std::string msg;
+        std::vector<std::string> devices;
+        if (managerGetCarDevices(devices, msg)) resp = Success(vec2str(devices));
+        else resp = Fail(msg);
+        resp.response(client);
+        return resp;
+    }
+
+    Response httpGetCarDeviceWoBList(Request &request, const int &client) {
+        Response resp;  // GET/POST
+        try {
+            auto dict = request.json.value<json::dict_type>();
+            if (dict.find("seq") == dict.end() || dict.find("isBlack") == dict.end()) {
+                resp = Fail("seq, isBlack need");
+            } else {
+                std::vector<std::string> plates;
+                std::string msg, seq = dict["seq"].value<std::string>();
+                if (managerGetCarDeviceWoBList(seq, dict["isBlack"].value<bool>(), plates, msg))
+                    resp = Success(vec2str(plates));
+                else resp = Fail(msg);
+            }
+        } catch (std::exception &error) {
+            resp = Fail(error.what());
+        }
+        resp.response(client);
+        return resp;
+    }
+
+    Response httpInsertCarDeviceWoBList(Request &request, const int &client) {
+        Response resp;  // POST
+        if (request.method == "GET") resp = Fail("POST method only");
+        try {
+            auto dict = request.json.value<json::dict_type>();
+            if (
+                    dict.find("seq") == dict.end() ||
+                    dict.find("isBlack") == dict.end() ||
+                    dict.find("plates") == dict.end()
+                    ) {
+                resp = Fail("seq, isBlack, plates need in the first layer");
+            } else {
+                std::string seq = dict["seq"].value<std::string>(), msg;
+                bool isBlack = dict["isBlack"].value<bool>(), errHpn = false;
+                auto platesJson = dict["plates"].value<json::list_type>();
+                std::vector<Plate> plates(platesJson.size());
+                for (int i = 0; i < platesJson.size(); ++i) {
+                    auto dr = platesJson[i].value<json::dict_type>();
+                    if (
+                            dr.find("name") == dr.end() ||
+                            dr.find("plate") == dr.end() ||
+                            dr.find("timestamp") == dr.end()
+                            ) {
+                        resp = Fail("name, plate, timestamp need in plates list");
+                        errHpn = true;
+                        break;
+                    } else {
+                        plates[i] = {
+                                .name = dr["name"].value<std::string>(), .plate = dr["plate"].value<std::string>(),
+                                .timestamp = dr["timestamp"].value<json::int_type>()
+                        };
+                    }
+                }
+                if (!errHpn) {
+                    if (managerInsertCarDeviceWoBList(seq, isBlack, plates, msg)) {
+                        resp = Success(msg);
+                    } else resp = Fail(msg);
+                }
+            }
+        } catch (std::exception &error) {
+            resp = Fail(error.what());
+        }
+        resp.response(client);
+        return resp;
+    }
+
+    Response httpRemoveCarDeviceWoBList(Request &request, const int &client) {
+        Response resp;  // POST
+        if (request.method == "GET") resp = Fail("POST method only");
+        try {
+            auto dict = request.json.value<json::dict_type>();
+            if (
+                    dict.find("seq") == dict.end() ||
+                    dict.find("isBlack") == dict.end() ||
+                    dict.find("cids") == dict.end()
+                    ) {
+                resp = Fail("seq, isBlack, cids need");
+            } else {
+                std::string seq = dict["seq"].value<std::string>(), msg;
+                bool isBlack = dict["isBlack"].value<bool>();
+                auto cidsJson = dict["cids"].value<json::list_type>();
+                std::vector<int> cids(cidsJson.size());
+                for (int i = 0; i < cidsJson.size(); ++i) cids[i] = (int)cidsJson[i].value<json::int_type>();
+                if (managerRemoveCarDeviceWoBList(seq, isBlack, cids, msg)) {
+                    resp = Success(msg);
+                } else resp = Fail(msg);
+            }
+        } catch (std::exception &error) {
+            resp = Fail(error.what());
+        }
+        resp.response(client);
+        return resp;
+    }
+}
+
+// implement of export funcs
+namespace hm {
+    [[noreturn]] void StartHttpServer() {
+        gServerId = socket(AF_INET, SOCK_STREAM, 0);
+        if (gServerId == -1) {
+            LogM(Error, "can't start http server: 1");
+            throw "can't start http server: 1";  // NOLINT
+        }
+
+        sockaddr_in serverAddress{};
+        serverAddress.sin_family = AF_INET;
+        serverAddress.sin_addr.s_addr = INADDR_ANY;
+        serverAddress.sin_port = htons(gConfServerPort);
+
+        if (bind(gServerId, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) == -1) {
+            LogM(Error, "can't listen at port<%d>", gConfServerPort);
+            throw "can't listen at port";  // NOLINT
+        }
+
+        if (listen(gServerId, 5) == -1) {
+            LogM(Error, "can't start http server: 2");
+            close(gServerId);
+            throw "can't start http server: 2";  // NOLINT
+        }
+        Log(Info, "server running on: http://0.0.0.0:%d/", gConfServerPort);
+
+        while (true) {
+            sockaddr_in addr{};
+            socklen_t size = sizeof addr;
+            int client = accept(gServerId, (struct sockaddr *) &addr, &size);
+            if (client == -1) {
+                Log(Error, "got an unacceptable connection");
+                continue;
+            }
+            std::thread thread(RequestHandler, client);
+            thread.detach();
+        }
+    }
+
+    void StopHttpServer() {
+        close(gServerId);
+        Log(Info, "http server stopped ~");
+    }
+}

+ 12 - 0
inc/http.h

@@ -0,0 +1,12 @@
+#ifndef HM_HTTP_H
+#define HM_HTTP_H
+
+
+namespace hm {
+    [[noreturn]] void StartHttpServer();
+
+    void StopHttpServer();
+}
+
+
+#endif //HM_HTTP_H

+ 29 - 0
inc/items.cpp

@@ -0,0 +1,29 @@
+#include "items.h"
+
+namespace hm {
+    std::string User::getJsonStr() {
+        /* 13: timestamp: 2023-1-1_0:0:0=>1672502400000, 2100-1-1_0:0:0=>4102416000000
+         * 47: {"uid":"","name":"","password":"","timestamp":}
+         * */
+        size_t size = uid.size() + name.size() + password.size() + 60;
+        std::string res(size, 0);
+        sprintf(
+                res.data(), R"({"uid":"%s","name":"%s","password":"%s","timestamp":%ld})",
+                uid.c_str(), name.c_str(), password.c_str(), timestamp
+        );
+        return res;
+    }
+
+    std::string Plate::getJsonStr() {
+        /* 13: timestamp: 2023-1-1_0:0:0=>1672502400000, 2100-1-1_0:0:0=>4102416000000
+         * 42: {"cid":,"name":"","plate":"","timestamp":}
+         * */
+        size_t size = cid_s.size() + name.size() + plate.size() + 55;
+        std::string res(size, 0);
+        sprintf(
+                res.data(), R"({"cid":%d,"name":"%s","plate":"%s","timestamp":%ld})",
+                cid, name.c_str(), plate.c_str(), timestamp
+        );
+        return res;
+    }
+}

+ 22 - 0
inc/items.h

@@ -0,0 +1,22 @@
+#ifndef HM_ITEMS_H
+#define HM_ITEMS_H
+
+#include <string>
+namespace hm {
+    struct User {
+        std::string uid, name, password, face;
+        long timestamp;
+
+        std::string getJsonStr();
+    };
+
+    struct Plate {
+        std::string name, plate, cid_s;
+        int cid;
+        long timestamp;
+
+        std::string getJsonStr();
+    };
+}
+
+#endif //HM_ITEMS_H

+ 166 - 0
inc/json/parser.cpp

@@ -0,0 +1,166 @@
+#include <sstream>
+#include "parser.h"
+#include "utils.h"
+
+namespace json {
+    std::string bad_near(const std::string &type, const unsigned int &pos) {
+        std::ostringstream err;
+        err << "bad type of '" << type << "' near position [" << pos << ']';
+        return err.str();
+    }
+
+    std::string bad_near(const char &c, const unsigned int &pos) {
+        std::ostringstream err;
+        err << "bad character '" << c << "' near position [" << pos << ']';
+        return err.str();
+    }
+
+    char Parser::next_real() {
+        while (std::isspace(_str[_idx])) _idx++;
+        return _str[_idx];
+    }
+
+    bool Parser::is_str_end(unsigned const &pos) {
+        unsigned front = pos;
+        while (_str[front] == '\\') front--;
+        return (pos - front) % 2 == 0;
+    }
+
+    Value Parser::_parse() {
+        char next = next_real();
+
+        switch (next) {
+            case 'n':
+                return parse_null();
+            case 't':
+            case 'f':
+                return parse_bool();
+            case '-':
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                return parse_number();
+            case '"':
+                return parse_string();
+            case '[':
+                return parse_list();
+            case '{':
+                return parse_dict();
+            default:
+                throw std::logic_error(bad_near(_str[_idx], _idx));
+        }
+    }
+
+    Value Parser::parse_null() {
+        if (_str.compare(_idx, 4, "null") == 0) {
+            _idx += 4;
+            return {};
+        }
+        throw std::logic_error(bad_near("null", _idx));
+    }
+
+    Value Parser::parse_bool() {
+        if (_str.compare(_idx, 4, "true") == 0) {
+            _idx += 4;
+            return Value(true);
+        }
+        if (_str.compare(_idx, 5, "false") == 0) {
+            _idx += 5;
+            return Value(false);
+        }
+        throw std::logic_error(bad_near("bool", _idx));
+    }
+
+    Value Parser::parse_number() {
+        unsigned start = _idx;
+        if (_str[_idx] == '-') _idx++;
+        if (std::isdigit(_str[_idx])) {
+            while (std::isdigit(_str[_idx])) _idx++;
+            // decimal
+            if (_str[_idx] == '.') {
+                _idx++;
+                if (std::isdigit(_str[_idx])) {
+                    while (std::isdigit(_str[_idx])) _idx++;
+                    return Value(std::strtod(_str.c_str() + start, nullptr));
+                } else throw std::logic_error(bad_near("number", _idx));
+            }
+            if (_str[_idx] != '.')
+                return Value(std::strtol(_str.c_str() + start, nullptr, 10));
+        } else throw std::logic_error(bad_near("number", _idx));
+        // 不会到这里
+        return {};
+    }
+
+    Value Parser::parse_string() {
+        unsigned start = ++_idx;
+        auto end = _str.find('"', _idx);
+        if (end != std::string::npos) {
+            // 可能是 \" 而不是真正的字符串结尾
+            while (true) {
+                if (is_str_end(end - 1)) break;
+                end = _str.find('"', end + 1);
+                if (end == std::string::npos) throw std::logic_error(bad_near("string", _idx));
+            }
+            _idx = end + 1;
+            return Value(_str.substr(start, end - start));
+        }
+        throw std::logic_error(bad_near("string", _idx));
+    }
+
+    Value Parser::parse_list() {
+        std::vector<Value> list;
+        _idx++;
+        char c;
+        while (true) {
+            c = next_real();
+            if (c == ',') _idx++;
+            else if (c == ']') {
+                _idx++;
+                return Value(list);
+            } else list.push_back(_parse());
+        }
+    }
+
+    Value Parser::parse_dict() {
+        std::map<std::string, Value> dict;
+        _idx++;
+        char c;
+        string_type key;
+        while (true) {
+            c = next_real();
+            if (c == '"') {
+                key = parse_string().value<string_type>();
+                if (next_real() == ':') {
+                    _idx++;
+                    dict[key] = _parse();
+                } else throw std::logic_error(bad_near("dict", _idx));
+            } else if (c == ',') _idx++;
+            else if (c == '}') {
+                _idx++;
+                return Value(dict);
+            } else throw std::logic_error(bad_near("dict", _idx));
+        }
+    }
+
+    Parser::Parser() {
+        _str = "";
+        _idx = 0;
+    }
+
+    Value Parser::parse(const std::string &str) {
+        _str = hm::trim(str);
+        auto max = _str.size() - 1;
+        if (max < 0) throw std::logic_error("blank string");
+        if (!((_str[0] == '{' && _str[max] == '}') || (_str[0] == '[' && _str[max] == ']')))
+            throw std::logic_error("bad json string");
+        _idx = 0;
+        return _parse();
+    }
+}

+ 37 - 0
inc/json/parser.h

@@ -0,0 +1,37 @@
+#ifndef HM_JSON_PARSER_H
+#define HM_JSON_PARSER_H
+
+#include "value.h"
+
+namespace json {
+    class Parser {
+    private:
+        std::string _str;
+        unsigned _idx;
+
+        char next_real();
+
+        bool is_str_end(unsigned const &pos);
+
+        Value _parse();
+
+        Value parse_null();
+
+        Value parse_bool();
+
+        Value parse_number();
+
+        Value parse_string();
+
+        Value parse_list();
+
+        Value parse_dict();
+
+    public:
+        Parser();
+
+        Value parse(const std::string &str);
+    };
+}
+
+#endif //HM_JSON_PARSER_H

+ 185 - 0
inc/json/value.cpp

@@ -0,0 +1,185 @@
+#include <sstream>
+#include "value.h"
+
+namespace json {
+    Value::Value() {
+        _type = NullType;
+        _data = "null";
+    }
+
+    Value::Value(bool_type const &value) {
+        _type = BoolType;
+        _data = value;
+    }
+
+    Value::Value(int const &value) {
+        _type = IntType;
+        _data = (int_type)value;
+    }
+
+    Value::Value(int_type const &value) {
+        _type = IntType;
+        _data = value;
+    }
+
+    Value::Value(decimal_type const &value) {
+        _type = DecimalType;
+        _data = value;
+    }
+
+    Value::Value(string_type const &value) {
+        _type = StringType;
+        _data = value;
+    }
+
+    Value::Value(const char *value) {
+        _type = StringType;
+        _data = std::string(value);
+    }
+
+    Value::Value(list_type const &value) {
+        _type = ListType;
+        _data = value;
+    }
+
+    Value::Value(dict_type const &value) {
+        _type = DictType;
+        _data = value;
+    }
+
+    void *Value::_value() {
+        switch (_type) {
+            case NullType:
+                // null: value<string>, type_id<char>, variant has no <char> in it.
+                return std::get_if<string_type>(&_data);
+            case BoolType:
+                return std::get_if<bool_type>(&_data);
+            case IntType:
+                return std::get_if<int_type>(&_data);
+            case DecimalType:
+                return std::get_if<decimal_type>(&_data);
+            case StringType:
+                return std::get_if<string_type>(&_data);
+            case ListType:
+                return std::get_if<list_type>(&_data);
+            case DictType:
+                return std::get_if<dict_type>(&_data);
+            default:
+                // will not be here
+                throw std::logic_error("Got an unrecognized type_id");
+        }
+    }
+
+    constexpr TYPE Value::type_id() const {
+        return _type;
+    }
+
+    std::string Value::type_name() const {
+        return _typeIdToName[_type];
+    }
+
+    constexpr bool Value::is_basic() const {
+        return _type == NullType ||
+               _type == BoolType ||
+               _type == IntType ||
+               _type == DecimalType ||
+               _type == StringType;
+    }
+
+    std::string Value::str(bool const &format_need, int const &indent, int layer) {
+        void *v = _value();
+        std::ostringstream buff;
+
+        switch (_type) {
+            case NullType:
+                buff << "null";
+                break;
+            case BoolType:
+                if (GET_VALUE(bool_type, v)) buff << "true";
+                else buff << "false";
+                break;
+            case IntType:
+                buff << GET_VALUE(int_type, v);
+                break;
+            case DecimalType:
+                buff << GET_VALUE(decimal_type, v);
+                break;
+            case StringType:
+                buff << '"' << GET_VALUE(string_type, v) << '"';
+                break;
+            case ListType: {
+                list_type &list = GET_VALUE(list_type, v);
+                buff << '[';
+                bool first = true;
+                for (Value &item: list) {
+                    if (first) {
+                        first = false;
+                        if (format_need) buff << item.str(format_need, indent, layer + 1);
+                        else buff << item.str();
+                    } else {
+                        if (format_need) buff << ", " << item.str(format_need, indent, layer + 1);
+                        else buff << ',' << item.str();
+                    }
+                }
+                buff << ']';
+                break;
+            }
+            case DictType: {
+                dict_type &dict = GET_VALUE(dict_type, v);
+                if (format_need) {
+                    buff << "{\n";
+                    for (auto it = dict.begin(); it != dict.end(); ++it) {
+                        if (it != dict.begin()) buff << ",\n";
+                        for (int i = 0; i < layer * indent; ++i) buff << ' ';
+                        buff << '"' << it->first << "\": " << it->second.str(format_need, indent, layer + 1);
+                    }
+                    buff << '\n';
+                    for (int i = 0; i < (layer - 1) * indent; ++i) buff << ' ';
+                    buff << '}';
+                } else {
+                    buff << '{';
+                    for (auto it = dict.begin(); it != dict.end(); it++) {
+                        if (it != dict.begin()) buff << ',';
+                        buff << '"' << it->first << "\":" << it->second.str();
+                    }
+                    buff << '}';
+                }
+                break;
+            }
+            default:
+                // will not be here
+                throw std::logic_error("Got an unrecognized type_id");
+        }
+        return buff.str();
+    }
+
+    void Value::push_back(Value const &val) {
+        auto &list = value<list_type>();
+        list.push_back(val);
+    }
+
+    void Value::pop_back() {
+        auto &list = value<list_type>();
+        list.pop_back();
+    }
+
+    void Value::erase(int const &index) {
+        auto &list = value<list_type>();
+        list.erase(list.begin() + index);
+    }
+
+    void Value::erase(std::string const &key) {
+        auto &dict = value<dict_type>();
+        if (dict.find(key) != dict.end()) dict.erase(key);
+    }
+
+    Value &Value::operator[](int const &index) {
+        auto &list = value<list_type>();
+        return list.at(index);
+    }
+
+    Value &Value::operator[](std::string const &key) {
+        auto &dict = value<dict_type>();
+        return dict[key];
+    }
+}

+ 104 - 0
inc/json/value.h

@@ -0,0 +1,104 @@
+#ifndef HM_JSON_VALUE_H
+#define HM_JSON_VALUE_H
+
+#define IS_TYPE(type_a, type_b) std::is_same<type_a, type_b>::value
+#define GET_VALUE(type, pointer) *((type *) pointer)
+#define DIFFERENT_TYPE_ERR(type_name) std::logic_error("Error: expected type is <" + type_name + ">")
+
+#include <string>
+#include <vector>
+#include <map>
+#include <variant>
+#include <stdexcept>
+
+namespace json {
+    class Value;
+
+    enum TYPE {
+        NullType, BoolType, IntType, DecimalType, StringType, ListType, DictType
+    };
+    std::string const _typeIdToName[] = {"Null", "Bool", "Int", "Decimal", "String", "List", "Dict"};  // NOLINT
+
+    using null_type = char;
+    using bool_type = bool;
+    using int_type = long;
+    using decimal_type = double;
+    using string_type = std::string;
+    using list_type = std::vector<Value>;
+    using dict_type = std::map<std::string, Value>;
+
+    class Value {
+    public:
+        Value();
+
+        explicit Value(bool_type const &value);
+
+        explicit Value(int const &value);
+
+        explicit Value(int_type const &value);
+
+        explicit Value(decimal_type const &value);
+
+        explicit Value(string_type const &value);
+
+        explicit Value(const char *value);
+
+        explicit Value(list_type const &value);
+
+        explicit Value(dict_type const &value);
+
+        template<class T>
+        [[nodiscard]] T &value();
+
+        [[nodiscard]] constexpr TYPE type_id() const;
+
+        [[nodiscard]] std::string type_name() const;
+
+        [[nodiscard]] constexpr bool is_basic() const;
+
+        [[nodiscard]] std::string str(bool const &format_need = false, int const &indent = 2, int layer = 1);
+
+        void push_back(Value const &value);
+
+        void pop_back();
+
+        void erase(int const &index);
+
+        void erase(std::string const &key);
+
+        Value &operator[](int const &index);
+
+        Value &operator[](std::string const &key);
+
+    private:
+        // null: string
+        using data_type = std::variant<bool_type, int_type, decimal_type, string_type, list_type, dict_type>;
+        TYPE _type;
+        data_type _data;
+
+        void *_value();
+    };
+
+    template<class T>
+    T &Value::value() {
+        if constexpr (IS_TYPE(T, null_type)) {
+            if (_type != NullType) throw DIFFERENT_TYPE_ERR(_typeIdToName[NullType]);
+        } else if constexpr (IS_TYPE(T, bool_type)) {
+            if (_type != BoolType) throw DIFFERENT_TYPE_ERR(_typeIdToName[BoolType]);
+        } else if constexpr (IS_TYPE(T, int_type)) {
+            if (_type != IntType) throw DIFFERENT_TYPE_ERR(_typeIdToName[IntType]);
+        } else if constexpr (IS_TYPE(T, decimal_type)) {
+            if (_type != DecimalType) throw DIFFERENT_TYPE_ERR(_typeIdToName[DecimalType]);
+        } else if constexpr (IS_TYPE(T, string_type)) {
+            if (_type != StringType) throw DIFFERENT_TYPE_ERR(_typeIdToName[StringType]);
+        } else if constexpr (IS_TYPE(T, list_type)) {
+            if (_type != ListType) throw DIFFERENT_TYPE_ERR(_typeIdToName[ListType]);
+        } else if constexpr (IS_TYPE(T, dict_type)) {
+            if (_type != DictType) throw DIFFERENT_TYPE_ERR(_typeIdToName[DictType]);
+        }
+
+        return *((T *) _value());
+    }
+}
+
+#endif //HM_JSON_VALUE_H

+ 223 - 0
inc/manager.cpp

@@ -0,0 +1,223 @@
+#include <unistd.h>
+#include <cstring>
+#include <thread>
+
+#include "utils.h"
+#include "manager.h"
+
+// declare of scoped vars and funcs
+namespace hm {
+    std::mutex gDeviceMtx;
+    std::vector<std::unique_ptr<Device>> gDevicesVec;
+    bool gSdkOk = false;
+    std::vector<LLONG> gSearchHandlers;
+
+    void disconnectFunc(LLONG loginId, char *dIp, LONG dPort, LDWORD userInfo);
+
+    void reconnectFunc(LLONG loginId, char *dIp, LONG dPort, LDWORD userInfo);
+
+    [[noreturn]] void ManagerLoop();
+
+    void startSearch();
+
+    void stopSearch();
+
+    void onDeviceFound(LLONG handler, DEVICE_NET_INFO_EX2 *deviceInfo, void *userData);
+
+    bool deviceInclude(const std::string &seq);
+
+
+    bool checkDevice(const Device *device, const bool &isFace, std::string &msg);
+
+    Device *getDeviceBySeq(const std::string &seq);
+
+    Device *getDeviceByLoginId(const LLONG &loginId);
+}
+
+// implement of scoped funcs
+namespace hm {
+    void disconnectFunc(LLONG loginId, char *dIp, LONG dPort, LDWORD userInfo) {
+        Log(Info, "loginId: 0x%lx, device[ip: %s, port: %d], user: %ld", loginId, dIp, dPort, userInfo);
+        Device *device = getDeviceByLoginId(loginId);
+        if (device != nullptr) device->online = false;
+    }
+
+    void reconnectFunc(LLONG loginId, char *dIp, LONG dPort, LDWORD userInfo) {
+        Log(Info, "loginId: 0x%lx, device[ip: %s, port: %d], user: %ld", loginId, dIp, dPort, userInfo);
+        Device *device = getDeviceByLoginId(loginId);
+        if (device != nullptr) device->online = true;
+    }
+
+    [[noreturn]] void ManagerLoop() {
+        bool first = true;
+        while (true) {
+            startSearch();
+            stopSearch();
+            for (const auto &itr: gDevicesVec) {
+                itr->Start();
+                if (itr->online) {
+                    itr->isFaceDevice ?
+                    itr->queryFaceUsers() :
+                    (itr->queryCarWoBList(true), itr->queryCarWoBList(false));
+                }
+            }
+            if (first) {
+                first = false;
+                Log(Info, "All device managed");
+            }
+            sleep(gConfSearchGap);
+        }
+    }
+
+    void startSearch() {
+        NET_IN_STARTSERACH_DEVICE pInBuf = {0};
+        NET_OUT_STARTSERACH_DEVICE pOutBuf = {0};
+        pInBuf.dwSize = sizeof(NET_IN_STARTSERACH_DEVICE);
+        pInBuf.cbSearchDevices = onDeviceFound;
+        pInBuf.pUserData = nullptr;
+        pOutBuf.dwSize = sizeof(NET_OUT_STARTSERACH_DEVICE);
+
+        std::vector<std::string> localIPs = getIpList();
+        for (const std::string &ip : localIPs) {
+            memset(pInBuf.szLocalIp, 0, MAX_LOCAL_IP_LEN);
+            strncpy(pInBuf.szLocalIp, ip.c_str(), MAX_LOCAL_IP_LEN - 1);
+            LLONG handler = CLIENT_StartSearchDevicesEx(&pInBuf, &pOutBuf);
+            gSearchHandlers.push_back(handler);
+        }
+        Log(Trace, "search local area network device for %ds ...", gConfSearchDuration);
+        sleep(gConfSearchDuration);
+    }
+
+    void stopSearch() {
+        for (const LLONG handler: gSearchHandlers) CLIENT_StopSearchDevices(handler);
+        gSearchHandlers.clear();
+        Log(Trace, "search stopped ~");
+    }
+
+    void onDeviceFound(LLONG handler, DEVICE_NET_INFO_EX2 *deviceInfo, void *userData) {
+        std::lock_guard<std::mutex> lock(gDeviceMtx);
+        if (deviceInfo->stuDevInfo.iIPVersion == 4 && !deviceInclude(deviceInfo->stuDevInfo.szSerialNo)) {
+            gDevicesVec.push_back(std::make_unique<Device>(deviceInfo));
+            Log(
+                    Info, "new device found, {ip: %s, mac: %s, seq: %s, type: %s}",
+                    deviceInfo->stuDevInfo.szIP, deviceInfo->stuDevInfo.szMac, deviceInfo->stuDevInfo.szSerialNo,
+                    deviceInfo->stuDevInfo.szDetailType
+            );
+        }
+    }
+
+    bool deviceInclude(const std::string &seq) {
+        for (const auto &itr : gDevicesVec) if (itr->seq == seq) return true;
+        return false;
+    }
+
+    bool checkDevice(const Device *device, const bool &isFace, std::string &msg) {
+        if (device == nullptr) {
+            msg = "device seq error";
+            return false;
+        }
+        if (!device->online) {
+            msg = "device offline";
+            return false;
+        }
+        if (device->isFaceDevice != isFace) {
+            msg = "device type error";
+            return false;
+        }
+        return true;
+    }
+
+    Device *getDeviceBySeq(const std::string &seq) {
+        std::lock_guard<std::mutex> lock(gDeviceMtx);
+        for (const auto &itr : gDevicesVec) if (itr->seq == seq) return itr.get();
+        return nullptr;
+    }
+
+    Device *getDeviceByLoginId(const LLONG &loginId) {
+        std::lock_guard<std::mutex> lock(gDeviceMtx);
+        for (const auto &itr : gDevicesVec) if (itr->loginId == loginId) return itr.get();
+        return nullptr;
+    }
+}
+
+namespace hm {
+    void StartManager() {
+        gSdkOk = CLIENT_Init(disconnectFunc, 0);
+        CLIENT_SetAutoReconnect(reconnectFunc, 0);
+        CLIENT_SetConnectTime(1000, 3);
+        if (!gSdkOk) {
+            LogM(Error, "SDK init failed.");
+            throw std::logic_error("SDK init failed.");
+        }
+        Log(Trace, "SDK init success");
+
+        std::thread thread(ManagerLoop);
+        thread.detach();
+    }
+
+    bool managerGetFaceDevices(std::vector<std::string> &devices, std::string &msg) {
+        for (const auto &itr : gDevicesVec) {
+            if (itr->isFaceDevice) devices.push_back(itr->getJsonStr());
+        }
+        return true;
+    }
+
+    bool managerGetFaceDeviceUsers(const std::string &seq, std::vector<std::string> &users, std::string &msg) {
+        Device *device = getDeviceBySeq(seq);
+        if (!checkDevice(device, true, msg)) return false;
+        return device->getFaceUsers(users, msg);
+    }
+
+    bool managerInsertFaceDeviceUser(const std::string &seq, const User &user, std::string &msg) {
+        Device *device = getDeviceBySeq(seq);
+        if (!checkDevice(device, true, msg)) return false;
+        return device->insertFaceUser(user, msg);
+    }
+
+    bool managerRemoveFaceDeviceUser(const std::string &seq, const std::string &uid, std::string &msg) {
+        Device *device = getDeviceBySeq(seq);
+        if (!checkDevice(device, true, msg)) return false;
+        return device->removeFaceUser(uid, msg);
+    }
+
+    bool managerGetCarDevices(std::vector<std::string> &devices, std::string &msg) {
+        for (const auto &itr : gDevicesVec) {
+            if (!itr->isFaceDevice) devices.push_back(itr->getJsonStr());
+        }
+        return true;
+    }
+
+    bool managerGetCarDeviceWoBList(
+            const std::string &seq, const bool &isBlack, std::vector<std::string> &list, std::string &msg
+    ) {
+        Device *device = getDeviceBySeq(seq);
+        if (!checkDevice(device, false, msg)) return false;
+        return device->getCarWoBList(isBlack, list, msg);
+    }
+
+    bool managerInsertCarDeviceWoBList(
+            const std::string &seq, const bool &isBlack, std::vector<Plate> &list, std::string &msg
+    ) {
+        Device *device = getDeviceBySeq(seq);
+        if (!checkDevice(device, false, msg)) return false;
+        return device->insertCarWoBList(isBlack, list, msg);
+    }
+
+    bool managerRemoveCarDeviceWoBList(
+            const std::string &seq, const bool &isBlack, std::vector<int> &cids, std::string &msg
+    ) {
+        Device *device = getDeviceBySeq(seq);
+        if (!checkDevice(device, false, msg)) return false;
+        return device->removeCarWoBList(isBlack, cids, msg);
+    }
+
+    void StopManager() {
+        stopSearch();
+        gDevicesVec.clear();
+        if (gSdkOk) {
+            CLIENT_Cleanup();
+            gSdkOk = false;
+        }
+        Log(Info, "manager stopped ~");
+    }
+}

+ 37 - 0
inc/manager.h

@@ -0,0 +1,37 @@
+#ifndef HM_MANAGER_H
+#define HM_MANAGER_H
+
+#include <vector>
+#include <string>
+#include "device.h"
+
+namespace hm {
+    void StartManager();
+
+    bool managerGetFaceDevices(std::vector<std::string> &devices, std::string &msg);
+
+    bool managerGetFaceDeviceUsers(const std::string &seq, std::vector<std::string> &user, std::string &msg);
+
+    bool managerInsertFaceDeviceUser(const std::string &seq, const User &user, std::string &msg);
+
+    bool managerRemoveFaceDeviceUser(const std::string &seq, const std::string &uid, std::string &msg);
+
+    bool managerGetCarDevices(std::vector<std::string> &devices, std::string &msg);
+
+    bool managerGetCarDeviceWoBList(
+            const std::string &seq, const bool &isBlack, std::vector<std::string> &list, std::string &msg
+    );
+
+    bool managerInsertCarDeviceWoBList(
+            const std::string &seq, const bool &isBlack, std::vector<Plate> &list, std::string &msg
+    );
+
+    bool managerRemoveCarDeviceWoBList(
+            const std::string &seq, const bool &isBlack, std::vector<int> &cids, std::string &msg
+    );
+
+    void StopManager();
+}
+
+
+#endif //HM_MANAGER_H

+ 220 - 0
inc/utils.cpp

@@ -0,0 +1,220 @@
+#include <sstream>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <ifaddrs.h>
+#include <curl/curl.h>
+#include <fstream>
+#include "utils.h"
+
+namespace hm {
+    Level gConfLogLevel = Warn;
+    std::string gConfFaceRemote = "http://127.0.0.1/", gConfCarRemote = "http://127.0.0.1/",  // NOLINT
+    gConfLoginUser = "<username here>", gConfLoginPass = "<password here>";  // NOLINT
+    unsigned int gConfSearchGap = 60, gConfSearchDuration = 4, gConfServerPort = 8040;
+
+    void ReadConfig(const char *path) {
+        std::ifstream file(path);
+        if (!file) {
+            LogM(Error, "config file \"%s\" open failed.", path);
+        } else {
+            std::string line, key, value;
+            while (std::getline(file, line)) {
+                if (line.empty() || line.size() == 1 || line[0] == '#') continue;
+                if (splitKeyValue(line, key, value)) {
+                    if (key == "faceRemote") gConfFaceRemote = value;
+                    else if (key == "carRemote") gConfCarRemote = value;
+                    else if (key == "user") gConfLoginUser = value;
+                    else if (key == "pass") gConfLoginPass = value;
+                    else if (key == "logLevel") gConfLogLevel = (Level) std::stoi(value);
+                    else if (key == "searchGap") gConfSearchGap = std::stoi(value);
+                    else if (key == "searchDuration") gConfSearchDuration = std::stoi(value);
+                    else if (key == "serverPort") gConfServerPort = std::stoi(value);
+                    else
+                        LogM(Warn, "unrecognized config key[%s], value[%s]", key.c_str(), value.c_str());
+                }
+            }
+        }
+        Log(
+                Trace, "{faceRemote:%s, carRemote:%s, user:%s, pass:%s, logLevel:%d, port:%d, searchGap:%d, searchDuration:%d}",
+                gConfFaceRemote.c_str(), gConfCarRemote.c_str(), gConfLoginUser.c_str(), gConfLoginPass.c_str(),
+                gConfLogLevel, gConfServerPort, gConfSearchGap, gConfSearchDuration
+        );
+    }
+
+    const char *logLevelName(const Level &level) {
+        switch (level) {
+            case Trace:
+                return "TRACE";
+            case Info:
+                return "INFO";
+            case Warn:
+                return "WARN";
+            case Error:
+                return "ERROR";
+            default:
+                return "NULL";
+        }
+    }
+
+    bool getLogStatus(const Level &level) {
+        return gConfLogLevel && level >= gConfLogLevel;
+    }
+
+    bool startsWith(const std::string &str, const std::string &start) {
+        if (str.length() < start.length()) {
+            return false;
+        }
+        return str.compare(0, start.length(), start) == 0;
+    }
+
+    bool endsWith(const std::string &str, const std::string &end) {
+        if (str.length() < end.length()) {
+            return false;
+        }
+        return str.compare(str.length() - end.length(), end.length(), end) == 0;
+    }
+
+    std::string trim(const std::string &str) {
+        unsigned start = 0, end = str.size() - 1;
+        while (std::isspace(str[start])) start++;
+        while (std::isspace(str[end])) end--;
+        return str.substr(start, end - start + 1);
+    }
+
+    bool splitKeyValue(const std::string &src, std::string &key, std::string &value) {
+        std::stringstream ss(src);
+        std::string token;
+        if (std::getline(ss, token, '=') && !token.empty()) {
+            key = trim(token);
+            if (std::getline(ss, token)) {
+                value = trim(token);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    std::vector<std::string> getIpList() {
+        std::vector<std::string> ipList;
+        struct ifaddrs *ifap, *ifa;
+        if (getifaddrs(&ifap) == -1) {
+            return ipList;
+        }
+        for (ifa = ifap; ifa != nullptr; ifa = ifa->ifa_next) {
+            if (ifa->ifa_addr == nullptr) {
+                continue;
+            }
+            if (ifa->ifa_addr->sa_family == AF_INET) {
+                auto *addr = (struct sockaddr_in *) ifa->ifa_addr;
+                char localIP[INET_ADDRSTRLEN];
+                inet_ntop(AF_INET, &(addr->sin_addr), localIP, INET_ADDRSTRLEN);
+                ipList.emplace_back(localIP);
+            }
+        }
+        freeifaddrs(ifap);
+        return ipList;
+    }
+
+    int imageRead(const std::string &path, char *&buffer) {
+        std::ifstream img(path, std::ios::binary);
+        img.seekg(0, std::ios::end);
+        int size = (int) img.tellg();
+        img.seekg(0, std::ios::beg);
+        buffer = new char[size];
+        img.read(buffer, size);
+        return size;
+    }
+
+    int imgcpy(char *&dest, const std::string &src) {
+        int size = (int) src.size();
+        dest = new char[size];
+        for (int i = 0; i < size; ++i) dest[i] = src[i];
+        return size;
+    }
+
+    void imageSave(const std::string &path, char *&start, const int &size) {
+        if (size > 0 && start != nullptr) {
+            std::ofstream out(path, std::ios::binary);
+            if (!out.is_open()) return;
+            out.write(start, size);
+            out.close();
+        }
+    }
+
+    void imageSave(const std::string &path, unsigned char *&start, const unsigned int &size) {
+        if (size > 0 && start != nullptr) {
+            std::ofstream out(path, std::ios::binary);
+            if (!out.is_open()) return;
+            out.write((char *) start, size);
+            out.close();
+        }
+    }
+
+    time_t toTimestamp(
+            const unsigned int &year, const unsigned int &mon, const unsigned int &day,
+            const unsigned int &hour, const unsigned int &min, const unsigned int &sec
+    ) {
+        struct tm info = {
+                .tm_sec = (int) sec, .tm_min = (int) min, .tm_hour = (int) hour,
+                .tm_mday = (int) day, .tm_mon = (int) mon - 1, .tm_year = (int) year - 1900
+        };
+        return mktime(&info);
+    }
+
+    size_t onPostResponse(void *contents, size_t size, size_t nMem, std::string *output) {
+        size_t total_size = size * nMem;
+        output->append(static_cast<char *>(contents), total_size);
+        return total_size;
+    }
+
+    // 22(21+1) errors here, never mind, it's ok anyway.
+    void dataUpload(
+            const std::string &url, const std::map<std::string, std::string> &data,
+            unsigned char *&imgBuf, const unsigned int &bufSize
+    ) {
+        CURL *curl = curl_easy_init();
+        if (!curl) {
+            Log(Error, "CURL initialization failed.");
+            return;
+        }
+        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3);
+        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+        curl_easy_setopt(curl, CURLOPT_POST, 1);
+        struct curl_httppost *form = nullptr;
+        struct curl_httppost *last = nullptr;
+        // json data
+        for (const auto &it : data) {
+            curl_formadd(
+                    &form, &last, CURLFORM_COPYNAME, it.first.c_str(),
+                    CURLFORM_COPYCONTENTS, it.second.c_str(), CURLFORM_END
+            );
+        }
+        // image
+        curl_formadd(
+                &form, &last, CURLFORM_COPYNAME, "image", CURLFORM_BUFFER, "image.jpg",
+                CURLFORM_BUFFERPTR, imgBuf, CURLFORM_BUFFERLENGTH, bufSize, CURLFORM_END
+        );
+        curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
+        // response
+        std::string response;
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, onPostResponse);
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
+        CURLcode res = curl_easy_perform(curl);
+        if (res == CURLE_OPERATION_TIMEDOUT) {
+            Log(Warn, "Connection Timeout");
+        } else if (res == CURLE_OK) {
+            long code;
+            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
+            if (code == 200) {
+                Log(Trace, "upload success with: %s", response.c_str());
+            } else {
+                Log(Warn, "upload fail with: code[%ld], msg: %s", code, response.c_str());
+            }
+        } else {
+            Log(Error, "HTTP request failed: %s", curl_easy_strerror(res));
+        }
+        curl_easy_cleanup(curl);
+        curl_formfree(form);
+    }
+}

+ 58 - 0
inc/utils.h

@@ -0,0 +1,58 @@
+#ifndef HM_UTILS_H
+#define HM_UTILS_H
+
+#include <cstdio>
+#include <string>
+#include <vector>
+#include <map>
+
+#define LogM(l, x...) printf("[HmGate|%5s] %16s : ",logLevelName(l),__FUNCTION__),printf(x),printf("\n")
+#define Log(l, x...) if (getLogStatus(l)) LogM(l, x)
+
+namespace hm {
+    enum Level {
+        Closed, Trace, Info, Warn, Error
+    };
+    extern Level gConfLogLevel;
+    extern std::string gConfFaceRemote, gConfCarRemote, gConfLoginUser, gConfLoginPass;
+    extern unsigned int  gConfSearchGap, gConfSearchDuration, gConfServerPort;
+
+    void ReadConfig(const char *path);
+
+    const char *logLevelName(const Level &level);
+
+    bool getLogStatus(const Level &level);
+
+    bool startsWith(const std::string &str, const std::string &start);
+
+    bool endsWith(const std::string &str, const std::string &end);
+
+    std::string trim(const std::string &str);
+
+    bool splitKeyValue(const std::string &src, std::string &key, std::string &value);
+
+    std::vector<std::string> getIpList();
+
+    int imageRead(const std::string &path, char *&buffer);
+
+    int imgcpy(char *&dest, const std::string &src);
+
+    void imageSave(const std::string &path, char *&start, const int &size);
+
+    void imageSave(const std::string &path, unsigned char *&start, const unsigned int &size);
+
+    time_t toTimestamp(
+            const unsigned int &year, const unsigned int &mon, const unsigned int &day,
+            const unsigned int &hour, const unsigned int &min, const unsigned int &sec
+    );
+
+    size_t onPostResponse(void *contents, size_t size, size_t nMem, std::string *output);
+
+    void dataUpload(
+            const std::string &url, const std::map<std::string, std::string> &data,
+            unsigned char *&imgBuf, const unsigned int &bufSize
+    );
+}
+
+
+#endif //HM_UTILS_H

二进制
lib/libdhnetsdk.so


+ 23 - 0
main.cpp

@@ -0,0 +1,23 @@
+#include "utils.h"
+#include "manager.h"
+#include "http.h"
+
+const char *getConfigFilePath(const int &count, const char **&args) {
+    if (count >= 2) return args[1];
+    return "config.ini";
+}
+
+// EVENT_IVS_TRAFFIC_STAY: 交通滞留事件
+// EVENT_IVS_STAYDETECTION: 停留事件
+// 手动拍照
+int main(const int count, const char **args) {
+    hm::ReadConfig(getConfigFilePath(count, args));
+    try {
+        hm::StartManager();
+        hm::StartHttpServer();
+    } catch (...) {
+        hm::StopHttpServer();
+        hm::StopManager();
+    }
+    return 0;
+}