|
@@ -122,8 +122,12 @@ void AlarmHistory::workerLoop() {
|
|
|
else
|
|
else
|
|
|
fprintf(stderr, "[AlarmHistory] 空帧跳过截图: %s\n",
|
|
fprintf(stderr, "[AlarmHistory] 空帧跳过截图: %s\n",
|
|
|
item.rel_path.c_str());
|
|
item.rel_path.c_str());
|
|
|
- insertRow(item.timestamp, item.channel, item.fence_name,
|
|
|
|
|
- item.class_name, item.confidence, item.rel_path);
|
|
|
|
|
|
|
+ if (item.kind == 1)
|
|
|
|
|
+ insertPoseRow(item.timestamp, item.channel, item.action,
|
|
|
|
|
+ item.score, item.rel_path);
|
|
|
|
|
+ else
|
|
|
|
|
+ insertRow(item.timestamp, item.channel, item.fence_name,
|
|
|
|
|
+ item.class_name, item.confidence, item.rel_path);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -159,9 +163,180 @@ void AlarmHistory::createTable() {
|
|
|
if (err) {
|
|
if (err) {
|
|
|
fprintf(stderr, "[AlarmHistory] createActionTable error: %s\n", err);
|
|
fprintf(stderr, "[AlarmHistory] createActionTable error: %s\n", err);
|
|
|
sqlite3_free(err);
|
|
sqlite3_free(err);
|
|
|
|
|
+ err = nullptr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 姿态报警表
|
|
|
|
|
+ const char* sql3 =
|
|
|
|
|
+ "CREATE TABLE IF NOT EXISTS pose_alarms ("
|
|
|
|
|
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
|
|
|
+ "timestamp TEXT NOT NULL,"
|
|
|
|
|
+ "channel INTEGER NOT NULL,"
|
|
|
|
|
+ "action TEXT NOT NULL,"
|
|
|
|
|
+ "score REAL NOT NULL,"
|
|
|
|
|
+ "screenshot_path TEXT NOT NULL"
|
|
|
|
|
+ ");";
|
|
|
|
|
+ sqlite3_exec(static_cast<sqlite3*>(db_), sql3, nullptr, nullptr, &err);
|
|
|
|
|
+ if (err) {
|
|
|
|
|
+ fprintf(stderr, "[AlarmHistory] createPoseTable error: %s\n", err);
|
|
|
|
|
+ sqlite3_free(err);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void AlarmHistory::recordPoseAlarm(int channel, const std::string& action,
|
|
|
|
|
+ float score, const cv::Mat& frame) {
|
|
|
|
|
+ Pending p;
|
|
|
|
|
+ p.timestamp = currentTimestamp();
|
|
|
|
|
+ p.channel = channel;
|
|
|
|
|
+ p.action = action;
|
|
|
|
|
+ p.score = score;
|
|
|
|
|
+ p.confidence = score;
|
|
|
|
|
+ p.class_name = action;
|
|
|
|
|
+ p.fence_name = "姿态报警";
|
|
|
|
|
+ p.rel_path = screenshot_dir_ + "/pose_" + generateFilename();
|
|
|
|
|
+ p.kind = 1;
|
|
|
|
|
+ if (!frame.empty()) p.frame = frame.clone();
|
|
|
|
|
+
|
|
|
|
|
+ std::lock_guard<std::mutex> lock(mtx_);
|
|
|
|
|
+ if (pending_.size() > 30) pending_.pop();
|
|
|
|
|
+ pending_.push(std::move(p));
|
|
|
|
|
+ pending_cv_.notify_one();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void AlarmHistory::insertPoseRow(const std::string& timestamp, int channel,
|
|
|
|
|
+ const std::string& action, float score,
|
|
|
|
|
+ const std::string& rel_path) {
|
|
|
|
|
+ std::lock_guard<std::mutex> lock(mtx_);
|
|
|
|
|
+ const char* sql =
|
|
|
|
|
+ "INSERT INTO pose_alarms (timestamp, channel, action, score, screenshot_path) "
|
|
|
|
|
+ "VALUES (?, ?, ?, ?, ?);";
|
|
|
|
|
+ sqlite3_stmt* stmt = nullptr;
|
|
|
|
|
+ if (sqlite3_prepare_v2(static_cast<sqlite3*>(db_), sql, -1, &stmt, nullptr) != SQLITE_OK) {
|
|
|
|
|
+ fprintf(stderr, "[AlarmHistory] pose prepare error: %s\n",
|
|
|
|
|
+ sqlite3_errmsg(static_cast<sqlite3*>(db_)));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ sqlite3_bind_text(stmt, 1, timestamp.c_str(), -1, SQLITE_STATIC);
|
|
|
|
|
+ sqlite3_bind_int(stmt, 2, channel);
|
|
|
|
|
+ sqlite3_bind_text(stmt, 3, action.c_str(), -1, SQLITE_STATIC);
|
|
|
|
|
+ sqlite3_bind_double(stmt, 4, score);
|
|
|
|
|
+ sqlite3_bind_text(stmt, 5, rel_path.c_str(), -1, SQLITE_STATIC);
|
|
|
|
|
+ if (sqlite3_step(stmt) != SQLITE_DONE)
|
|
|
|
|
+ fprintf(stderr, "[AlarmHistory] pose insert error: %s\n",
|
|
|
|
|
+ sqlite3_errmsg(static_cast<sqlite3*>(db_)));
|
|
|
|
|
+ sqlite3_finalize(stmt);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<PoseAlarmRecord> AlarmHistory::getPoseAlarms(
|
|
|
|
|
+ int limit, int offset, int channel, const std::string& action,
|
|
|
|
|
+ const std::string& start, const std::string& end) {
|
|
|
|
|
+ std::lock_guard<std::mutex> lock(mtx_);
|
|
|
|
|
+ std::vector<PoseAlarmRecord> result;
|
|
|
|
|
+ std::string sql =
|
|
|
|
|
+ "SELECT id, timestamp, channel, action, score, screenshot_path "
|
|
|
|
|
+ "FROM pose_alarms WHERE 1=1";
|
|
|
|
|
+ if (channel >= 0) sql += " AND channel = ?";
|
|
|
|
|
+ if (!action.empty()) sql += " AND action = ?";
|
|
|
|
|
+ if (!start.empty()) sql += " AND timestamp >= ?";
|
|
|
|
|
+ if (!end.empty()) sql += " AND timestamp <= ?";
|
|
|
|
|
+ sql += " ORDER BY timestamp DESC LIMIT ? OFFSET ?;";
|
|
|
|
|
+ sqlite3_stmt* stmt = nullptr;
|
|
|
|
|
+ if (sqlite3_prepare_v2(static_cast<sqlite3*>(db_), sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK)
|
|
|
|
|
+ return result;
|
|
|
|
|
+ int idx = 1;
|
|
|
|
|
+ auto bindStr = [&](const std::string& s) {
|
|
|
|
|
+ sqlite3_bind_text(stmt, idx++, s.c_str(), -1, SQLITE_TRANSIENT);
|
|
|
|
|
+ };
|
|
|
|
|
+ if (channel >= 0) sqlite3_bind_int(stmt, idx++, channel);
|
|
|
|
|
+ if (!action.empty()) bindStr(action);
|
|
|
|
|
+ if (!start.empty()) bindStr(start);
|
|
|
|
|
+ if (!end.empty()) bindStr(end);
|
|
|
|
|
+ sqlite3_bind_int(stmt, idx++, limit);
|
|
|
|
|
+ sqlite3_bind_int(stmt, idx++, offset);
|
|
|
|
|
+ while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
|
|
|
+ PoseAlarmRecord r;
|
|
|
|
|
+ r.id = sqlite3_column_int64(stmt, 0);
|
|
|
|
|
+ auto txt = [&](int c) {
|
|
|
|
|
+ auto p = sqlite3_column_text(stmt, c);
|
|
|
|
|
+ return p ? std::string(reinterpret_cast<const char*>(p)) : std::string();
|
|
|
|
|
+ };
|
|
|
|
|
+ r.timestamp = txt(1);
|
|
|
|
|
+ r.channel = sqlite3_column_int(stmt, 2);
|
|
|
|
|
+ r.action = txt(3);
|
|
|
|
|
+ r.score = static_cast<float>(sqlite3_column_double(stmt, 4));
|
|
|
|
|
+ r.screenshot_path = txt(5);
|
|
|
|
|
+ result.push_back(std::move(r));
|
|
|
|
|
+ }
|
|
|
|
|
+ sqlite3_finalize(stmt);
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int AlarmHistory::getPoseAlarmCount(int channel, const std::string& action,
|
|
|
|
|
+ const std::string& start,
|
|
|
|
|
+ const std::string& end) {
|
|
|
|
|
+ std::lock_guard<std::mutex> lock(mtx_);
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ std::string sql = "SELECT COUNT(*) FROM pose_alarms WHERE 1=1";
|
|
|
|
|
+ if (channel >= 0) sql += " AND channel = ?";
|
|
|
|
|
+ if (!action.empty()) sql += " AND action = ?";
|
|
|
|
|
+ if (!start.empty()) sql += " AND timestamp >= ?";
|
|
|
|
|
+ if (!end.empty()) sql += " AND timestamp <= ?;";
|
|
|
|
|
+ sqlite3_stmt* stmt = nullptr;
|
|
|
|
|
+ if (sqlite3_prepare_v2(static_cast<sqlite3*>(db_), sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) {
|
|
|
|
|
+ int idx = 1;
|
|
|
|
|
+ auto bindStr = [&](const std::string& s) {
|
|
|
|
|
+ sqlite3_bind_text(stmt, idx++, s.c_str(), -1, SQLITE_TRANSIENT);
|
|
|
|
|
+ };
|
|
|
|
|
+ if (channel >= 0) sqlite3_bind_int(stmt, idx++, channel);
|
|
|
|
|
+ if (!action.empty()) bindStr(action);
|
|
|
|
|
+ if (!start.empty()) bindStr(start);
|
|
|
|
|
+ if (!end.empty()) bindStr(end);
|
|
|
|
|
+ if (sqlite3_step(stmt) == SQLITE_ROW) count = sqlite3_column_int(stmt, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ sqlite3_finalize(stmt);
|
|
|
|
|
+ return count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool AlarmHistory::deletePoseAlarm(int64_t id) {
|
|
|
|
|
+ std::lock_guard<std::mutex> lock(mtx_);
|
|
|
|
|
+ std::string screenshot;
|
|
|
|
|
+ sqlite3_stmt* stmt = nullptr;
|
|
|
|
|
+ const char* sel = "SELECT screenshot_path FROM pose_alarms WHERE id = ?;";
|
|
|
|
|
+ if (sqlite3_prepare_v2(static_cast<sqlite3*>(db_), sel, -1, &stmt, nullptr) == SQLITE_OK) {
|
|
|
|
|
+ sqlite3_bind_int64(stmt, 1, id);
|
|
|
|
|
+ if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
|
|
|
+ auto p = sqlite3_column_text(stmt, 0);
|
|
|
|
|
+ if (p) screenshot = reinterpret_cast<const char*>(p);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ sqlite3_finalize(stmt);
|
|
|
|
|
+ if (!screenshot.empty() && fs::exists(screenshot)) fs::remove(screenshot);
|
|
|
|
|
+ const char* del = "DELETE FROM pose_alarms WHERE id = ?;";
|
|
|
|
|
+ sqlite3_prepare_v2(static_cast<sqlite3*>(db_), del, -1, &stmt, nullptr);
|
|
|
|
|
+ sqlite3_bind_int64(stmt, 1, id);
|
|
|
|
|
+ int rc = sqlite3_step(stmt);
|
|
|
|
|
+ sqlite3_finalize(stmt);
|
|
|
|
|
+ return rc == SQLITE_DONE;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void AlarmHistory::clearPoseAlarms() {
|
|
|
|
|
+ std::lock_guard<std::mutex> lock(mtx_);
|
|
|
|
|
+ sqlite3_stmt* stmt = nullptr;
|
|
|
|
|
+ const char* sel = "SELECT screenshot_path FROM pose_alarms;";
|
|
|
|
|
+ if (sqlite3_prepare_v2(static_cast<sqlite3*>(db_), sel, -1, &stmt, nullptr) == SQLITE_OK) {
|
|
|
|
|
+ while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
|
|
|
+ auto p = sqlite3_column_text(stmt, 0);
|
|
|
|
|
+ if (p) {
|
|
|
|
|
+ std::string path = reinterpret_cast<const char*>(p);
|
|
|
|
|
+ if (fs::exists(path)) fs::remove(path);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ sqlite3_finalize(stmt);
|
|
|
|
|
+ sqlite3_exec(static_cast<sqlite3*>(db_), "DELETE FROM pose_alarms;",
|
|
|
|
|
+ nullptr, nullptr, nullptr);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
void AlarmHistory::recordAction(const std::string& source,
|
|
void AlarmHistory::recordAction(const std::string& source,
|
|
|
const std::string& detail,
|
|
const std::string& detail,
|
|
|
const std::string& channels) {
|
|
const std::string& channels) {
|
|
@@ -411,4 +586,23 @@ void AlarmHistory::cleanup(int keep_days) {
|
|
|
sqlite3_bind_text(stmt, 1, cutoff, -1, SQLITE_STATIC);
|
|
sqlite3_bind_text(stmt, 1, cutoff, -1, SQLITE_STATIC);
|
|
|
sqlite3_step(stmt);
|
|
sqlite3_step(stmt);
|
|
|
sqlite3_finalize(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
+
|
|
|
|
|
+ // 姿态报警: 先删截图文件, 再删记录
|
|
|
|
|
+ const char* selPose = "SELECT screenshot_path FROM pose_alarms WHERE timestamp < ?;";
|
|
|
|
|
+ if (sqlite3_prepare_v2(static_cast<sqlite3*>(db_), selPose, -1, &stmt, nullptr) == SQLITE_OK) {
|
|
|
|
|
+ sqlite3_bind_text(stmt, 1, cutoff, -1, SQLITE_STATIC);
|
|
|
|
|
+ while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
|
|
|
+ auto p = sqlite3_column_text(stmt, 0);
|
|
|
|
|
+ if (p) {
|
|
|
|
|
+ std::string path = reinterpret_cast<const char*>(p);
|
|
|
|
|
+ if (fs::exists(path)) fs::remove(path);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ sqlite3_finalize(stmt);
|
|
|
|
|
+ const char* delPose = "DELETE FROM pose_alarms WHERE timestamp < ?;";
|
|
|
|
|
+ sqlite3_prepare_v2(static_cast<sqlite3*>(db_), delPose, -1, &stmt, nullptr);
|
|
|
|
|
+ sqlite3_bind_text(stmt, 1, cutoff, -1, SQLITE_STATIC);
|
|
|
|
|
+ sqlite3_step(stmt);
|
|
|
|
|
+ sqlite3_finalize(stmt);
|
|
|
}
|
|
}
|