Initial commit of secondary development sample code

This commit is contained in:
2026-01-19 10:39:22 +08:00
commit c2697affd9
66 changed files with 17277 additions and 0 deletions

View File

@ -0,0 +1,171 @@
## 一、功能概述
`Api` 类是一个用于处理检测结果、告警信息和告警视频的工具类。
[api_demo.py](api_demo.py) 为原始默认实现。
[api_demo_tcp.py](api_demo_tcp.py)实现最简单的tcp通讯如果需要自动重连队列等业务代码请自行实现。
此类名、方法名等框架是固定的,不可修改。你可以通过实现回调方法的具体逻辑(如 `send_result_callback``send_alert_callback``send_alert_video_callback`)以及配置类的属性(如 `ignore_result``ignore_alert` 等)来实现具体功能。
## 二、类的属性
`Api` 类**必须**包含以下主要属性,用于控制其行为:
| 属性名称 | 默认值 | 描述 |
| :------------------- | :----- | :----------------------------------------------------------- |
| `ignore_result` | `True` | 是否发送检测结果。`True` 表示不发送,`False` 表示发送。 |
| `ignore_alert` | `True` | 是否发送告警信息。`True` 表示不发送,`False` 表示发送。 |
| `draw_image` | `True` | 是否在告警图片上绘制告警信息。`True` 表示绘制,`False` 表示不绘制。 |
| `ignore_alert_video` | `True` | 是否发送告警视频。`True` 表示不发送,`False` 表示发送。 |
## 三、类的方法
`Api` 类**必须**包含以下三个方法,用于发送检测结果、告警信息和告警视频。可根据需求实现具体的回调方法。
### 1. 检测结果
- **方法**send_result_callback(self, result):
- **功能**:发送检测结果。
- **参数**
- `result`:检测结果的内容。具体格式和内容如下:
- **示例**
```json
{
"hit": false, //是否命中
"time": 1742458167.288579, //告警时间戳
"device": {
"id": "设备id",
"name": "设备名称",
"desc": "设备描述"
},
"source": {
"id": "数据源id",
"ipv4": "ip地址",
"desc": "数据源描述"
},
"alg": {
"name": "算法名称英文",
"ch_name": "算法名称中文",
"type": "general"
},
"reserved_data": {
"bbox": {
"rectangles": [
{
"xyxy": [668,562,790,656], //左上角、右下角坐标
"color": [0,0,255], //BGR颜色
"label": "未佩戴安全帽", //标签
"conf": 0.91, //置信度
"ext": {} //扩展字段
}
],
"polygons": {}, //多边形对象
"lines": {} //线段对象
},
"custom": {}
},
"hazard_level": "", //危险等级
}
```
### 2. 告警信息
- **方法**send_alert_callback(self, alert)
- **功能**:发送告警信息。
- **参数**
- `alert`:告警信息的内容。具体格式和内容如下:
- **示例**
```json
{
"id": "67dbcd3c5dc58a7aaa019e41", //告警id
"alert_time": 1742458171.808598, //告警时间戳
"device": {
"id": "设备id",
"name": "设备名称",
"desc": "设备描述"
},
"source": {
"id": "数据源id",
"ipv4": "ip地址",
"desc": "数据源描述"
},
"alg": {
"name": "算法名称英文",
"ch_name": "算法名称中文",
"type": "general"
},
"hazard_level": "", //危险等级
"image": "img_base64", //base64编码的图片数据
"reserved_data": {
"bbox": {
"rectangles": [
{
"xyxy": [668,560,790,656], //左上角、右下角坐标
"color": [0,0,255], //BGR颜色
"label": "未佩戴安全帽", //标签
"conf": 0.91, //置信度
"ext": {} //扩展字段
}
],
"polygons": {},//多边形对象
"lines": {} //线段对象
},
"custom": {}
}
}
```
### 3. 告警视频
- **方法**send_alert_video_callback(self, alert_video):
- **功能**:发送告警视频。
- **参数**
- `alert_video`:告警视频的内容。具体格式和内容如下:
- **示例**
```json
{
"id": "67dbcd3c5dc58a7aaa019e41", //告警id
"alert_time": 1742458171.808598, //告警时间戳
"device": {
"id": "设备id",
"name": "设备名称",
"desc": "设备描述"
},
"source": {
"id": "数据源id",
"ipv4": "ip地址",
"desc": "数据源描述"
},
"alg": {
"name": "算法名称英文",
"ch_name": "算法名称中文",
"type": "general"
},
"hazard_level": "", //危险等级
"video": "video_base64" //base64编码的视频数据
}
```
## 四、注意事项
1. **属性配置**:在调用发送方法之前,确保已经正确配置了类的属性,以启用或禁用所需的功能。
2. **方法实现**:默认情况下,`send_result_callback`、`send_alert_callback` 和 `send_alert_video_callback` 方法是空方法。在实际使用中,需要根据具体需求实现这些方法的逻辑,例如将数据发送到服务器。

View File

@ -0,0 +1,40 @@
class Api:
def __init__(self):
"""
Attributes:
self.ignore_result: 为True时不发送检测结果为False则发送检测结果
self.ignore_alert: 为True时不发送告警信息为False则发送告警信息
self.draw_image: 为True时告警图片会画上告警信息为False则不画
self.ignore_alert_video: 为True时不发送告警视频为False则发送
"""
self.ignore_result = True
self.ignore_alert = True
self.draw_image = True
self.ignore_alert_video = True
def send_result_callback(self, result):
"""
发送检测结果回调函数
Args:
result: 检测结果数据
Returns:
"""
pass
def send_alert_callback(self, alert):
"""
发送告警信息回调函数
Args:
alert: 告警数据
Returns:
"""
pass
def send_alert_video_callback(self, alert_video):
"""
发送告警视频回调函数
Args:
alert_video: 告警视频数据
Returns:
"""
pass

View File

@ -0,0 +1,68 @@
import json
import socket
# 导入日志
from logger import LOGGER
class Api:
"""
API类实现最简单的tcp通讯如果需要自动重连队列等业务代码请自行实现
"""
def __init__(self):
"""
Attributes:
self.ignore_result: 为True时不发送检测结果为False则发送检测结果
self.ignore_alert: 为True时不发送告警信息为False则发送告警信息
self.draw_image: 为True时告警图片会画上告警信息为False则不画
self.ignore_alert_video: 为True时不发送告警视频为False则发送
"""
self.ignore_result = True
self.ignore_alert = False
self.draw_image = True
self.ignore_alert_video = True
def send_result_callback(self, result):
"""
发送检测结果回调函数
Args:
result: 检测结果数据
Returns:
"""
pass
def send_alert_callback(self, alert):
"""
发送告警信息回调函数
Args:
alert: 告警数据
Returns:
"""
try:
LOGGER.info('发送TCP告警')
# 创建TCP socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# 连接到目标服务器(这里用示例地址和端口)
s.connect(('192.168.0.4', 10001))
# 为了方便查看日志去掉base64编码图片
alert.pop('image')
json_data = json.dumps(alert, ensure_ascii=False)
data = json_data.encode('utf-8')
# 发送完整数据
s.sendall(data)
LOGGER.info(f'告警已发送至TCP服务器: {data}')
except Exception as e:
LOGGER.error(f'发送TCP告警失败: {str(e)}')
def send_alert_video_callback(self, alert_video):
"""
发送告警视频回调函数
Args:
alert_video: 告警视频数据
Returns:
"""
pass

View File

@ -0,0 +1,83 @@
# HTTP告警推送
> **http-server-demo** 分为三个文件夹。
>
> 1. **headers** http请求头的demo代码
> 2. **http-server** http服务端接收告警推送(无token版本)
> 3. **http-server-token** http服务端接收告警推送(有token版本)
## headers
如果需要将盒子产生的告警推送到您自建平台,并且你的平台需要验证**token**,则需要用到该文件夹下的`headers_demo.py`文件。
你可自行修改`headers_demo.py`文件,并将此文件上传到盒子平台的【数据推送】-【告警】-【HTTP】-【配置token】。
`headers_demo.py`文件说明:
- 类名必须为`Headers`,继承`BaseHeaders`类。`BaseHeaders`类通过`api.http`导入
```python
from api.http import Headers as BaseHeaders
```
- 定义三个实例变量:`get_headers_url`、`timeout`、`interval`。
`get_headers_url`:指定获取`token`的**URL**地址。
`timeout`:指定获取`token`的超时时间(单位:秒)。
`interval`:定时刷新`headers`的时间间隔(单位:秒)。
```python
class Headers(BaseHeaders):
def __init__(self):
self.get_headers_url = None
self.timeout = 5
interval = 60 * 10
super().__init__(interval)
```
- 必须实现`_generate_headers`方法。返回请求头字典`headers`。返回示例:
```python
{'authorization': 'Bearer abcdefghijklmnopqrstuvwxyz'}
```
- 完整实例如下:
```python
import requests
from api.http import Headers as BaseHeaders
from logger import LOGGER
class Headers(BaseHeaders):
def __init__(self):
self.get_headers_url = None
self.timeout = 5
interval = 60 * 10
super().__init__(interval)
def _generate_headers(self):
try:
headers = {
'authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkXXX'
}
return headers
except:
LOGGER.exception('_generate_headers')
return None
```
## http-server
该文件夹为**http**服务端代码,提供**python**和**java**代码。它主要用于接收盒子的**http**告警推送。如果您需要验证盒子的**http**推送功能是否正常,可使用此文件夹进行测试。
运行该文件夹下的代码,即可开启一个**http**服务端。在盒子平台的【数据推送】-【告警】-【HTTP】中启用推送管理并填写**http**服务端地址,即可开启推送功能。
## http-server-token
此文件夹同**http-server**文件夹,只是增加了**token**验证功能。
你首先需把其下的`headers_demo1.py`或`headers_demo2.py`文件上传到盒子平台的【数据推送】-【告警】-【HTTP】-【配置TOKEN】。
`headers_demo1.py`通过调用url接口获取token。(盒子必须可以ping通该url文件中的`get_headers_url`变量为**http**服务端URL)
`headers_demo2.py`固定token。

View File

@ -0,0 +1,46 @@
import requests
from api.http import Headers as BaseHeaders
from logger import LOGGER
class Headers(BaseHeaders):
def __init__(self):
"""
初始化Headers类
- `self.get_headers_url`: 获取token的URL地址根据实际环境修改。
- `self.timeout`: 请求超时时间设置为5秒。
- `interval`: 定时刷新headers的时间间隔设置为10分钟60秒 * 10
"""
self.get_headers_url = None
self.timeout = 5
interval = 60 * 10
super().__init__(interval)
def _generate_headers(self):
"""
生成请求头的方法_generate_headers方法名不允许修改
通过向指定的URL发送GET请求获取token并将token添加到请求头中
:return: 请求头字典
"""
try:
# 定义请求参数
params = {
'arg1': 'xxx',
'arg2': 'xxx'
}
if self.get_headers_url is not None:
# 发送GET请求获取token
resp = requests.get(self.get_headers_url, params=params, timeout=self.timeout)
if resp.status_code == 200:
token = resp.text
headers = {
'authorization': 'Bearer {}'.format(token)
}
return headers
else:
LOGGER.error('Get headers failed')
return None
except:
LOGGER.exception('_generate_headers')
return None

View File

@ -0,0 +1,47 @@
import requests
from api.http import Headers as BaseHeaders
from logger import LOGGER
class Headers(BaseHeaders):
def __init__(self):
"""
初始化Headers类
- `self.get_headers_url`: 获取token的URL地址根据实际环境修改。
- `self.timeout`: 请求超时时间设置为5秒。
- `interval`: 定时刷新headers的时间间隔设置为10分钟60秒 * 10
"""
self.get_headers_url = 'http://192.168.1.75:10000/token'
self.timeout = 5
interval = 60 * 10
super().__init__(interval)
def _generate_headers(self):
"""
生成请求头的方法_generate_headers方法名不允许修改
通过向指定的URL发送GET请求获取token并将token添加到请求头中
:return: 请求头字典
"""
try:
# 定义请求参数
params = {
'arg1': 'xxx',
'arg2': 'xxx'
}
if self.get_headers_url is not None:
# 发送GET请求获取token
resp = requests.get(self.get_headers_url, params=params, timeout=self.timeout)
LOGGER.info('Get headers resp {}'.format(resp))
if resp.status_code == 200:
token = resp.text
headers = {
'authorization': 'Bearer {}'.format(token)
}
return headers
else:
LOGGER.error('Get headers failed')
return None
except:
LOGGER.exception('_generate_headers')
return None

View File

@ -0,0 +1,35 @@
import requests
from api.http import Headers as BaseHeaders
from logger import LOGGER
class Headers(BaseHeaders):
def __init__(self):
"""
初始化Headers类
- `self.get_headers_url`: 获取token的URL地址根据实际环境修改。
- `self.timeout`: 请求超时时间设置为5秒。
- `interval`: 定时刷新headers的时间间隔设置为10分钟60秒 * 10
"""
self.get_headers_url = None
self.timeout = 5
interval = 60 * 10
super().__init__(interval)
def _generate_headers(self):
"""
生成请求头的方法_generate_headers方法名不允许修改
固定http请求头authorization以及值可自定义
:return: 请求头字典
"""
try:
headers = {
'authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkXXX'
}
return headers
except:
LOGGER.exception('_generate_headers')
return None

View File

@ -0,0 +1,45 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping(path = "")
public class AlertController {
/**
* 目标平台接收告警及告警图片
* @param alertMsg
* @param authorization
*/
@PostMapping(path = "/alert")
public void getAlertMsg(@RequestBody AlertMsg alertMsg, @RequestHeader("Authorization") String authorization) {
log.info("authorization{}", authorization);
log.info("示例接收告警及告警图片:{}", alertMsg);
}
/**
* 目标平台接收告警及告警视频
*
* @param alertVideo
*/
@PostMapping(path = "/video")
public void getAlertVideo(@RequestBody AlertVideo alertVideo,@RequestHeader("Authorization") String authorization) {
log.info("authorization{}", authorization);
log.info("示例接收告警及告警视频:{}", alertVideo);
}
@GetMapping(path = "/token")
public String token() {
return RandomUtil.randomString(16);
}
}

View File

@ -0,0 +1,20 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class AlertMsg {
private String id;
@JsonProperty("alert_time")
private Double alertTime;
private Object device;
private Object source;
private Object alg;
private String image;
@JsonProperty("reserved_data")
private Object reservedData;
@JsonProperty("hazard_leve")
private String hazardLeve;
}

View File

@ -0,0 +1,18 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class AlertVideo {
private String id;
@JsonProperty("alert_time")
private Double alertTime;
private Object device;
private Object source;
private Object alg;
private String video;
@JsonProperty("hazard_leve")
private String hazardLeve;
}

View File

@ -0,0 +1,23 @@
import os
import sys
from flask import Flask
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
sys.path.append(CURRENT_PATH)
url_prefix = '/'
from app import alert
def create_app():
# 初始化Flask对象
app_ = Flask(__name__)
# 注册蓝图
app_.register_blueprint(alert.bp)
return app_
app = create_app()

View File

@ -0,0 +1,31 @@
import base64
import hashlib
import json
import secrets
import time
from flask import Blueprint, request
from app import url_prefix
bp = Blueprint('alert', __name__, url_prefix=url_prefix)
@bp.route('alert', methods=['POST'])
def post_alert():
# 获取token
auth_header = request.headers.get('authorization')
print(f"Authorization Header: {auth_header}")
data = json.loads(request.get_data().decode('utf-8'))
image = data.pop('image')
print(data)
with open('image.jpg', 'wb') as f:
f.write(base64.b64decode(image.encode('utf-8')))
return data
@bp.route('/token', methods=['GET'])
def gen_token():
print(request.args)
token = secrets.token_hex(32)
return token

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -0,0 +1,4 @@
from app import app
if '__main__' == __name__:
app.run(host='0.0.0.0', port=10000, debug=False)

View File

@ -0,0 +1,37 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping(path = "")
public class AlertController {
/**
* 目标平台接收告警及告警图片
*
* @param alertMsg
*/
@PostMapping(path = "/alert")
public void getAlertMsg(@RequestBody AlertMsg alertMsg) {
log.info("示例接收告警及告警图片:{}", alertMsg);
}
/**
* 目标平台接收告警及告警视频
*
* @param alertVideo
*/
@PostMapping(path = "/video")
public void getAlertVideo(@RequestBody AlertVideo alertVideo) {
log.info("示例接收告警及告警视频:{}", alertVideo);
}
}

View File

@ -0,0 +1,21 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class AlertMsg {
private String id;
@JsonProperty("alert_time")
private Double alertTime;
private Object device;
private Object source;
private Object alg;
private String image;
@JsonProperty("reserved_data")
private Object reservedData;
@JsonProperty("hazard_leve")
private String hazardLeve;
}

View File

@ -0,0 +1,19 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class AlertVideo {
private String id;
@JsonProperty("alert_time")
private Double alertTime;
private Object device;
private Object source;
private Object alg;
private String video;
@JsonProperty("hazard_leve")
private String hazardLeve;
}

View File

@ -0,0 +1,23 @@
import os
import sys
from flask import Flask
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
sys.path.append(CURRENT_PATH)
url_prefix = '/'
from app import alert
def create_app():
# 初始化Flask对象
app_ = Flask(__name__)
# 注册蓝图
app_.register_blueprint(alert.bp)
return app_
app = create_app()

View File

@ -0,0 +1,28 @@
import base64
import json
from flask import Blueprint, request
from app import url_prefix
bp = Blueprint('alert', __name__, url_prefix=url_prefix)
@bp.route('alert', methods=['POST'])
def post_alert():
data = json.loads(request.get_data().decode('utf-8'))
image = data.pop('image')
print(data)
with open('image.jpg', 'wb') as f:
f.write(base64.b64decode(image.encode('utf-8')))
return data
@bp.route('alert/video', methods=['POST'])
def post_alert_video():
data = json.loads(request.get_data().decode('utf-8'))
video = data.pop('video')
print(data)
with open('video.mp4', 'wb') as f:
f.write(base64.b64decode(video.encode('utf-8')))
return data

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

View File

@ -0,0 +1,4 @@
from app import app
if '__main__' == __name__:
app.run(host='0.0.0.0', port=10000, debug=False)

View File

@ -0,0 +1,107 @@
import json
import socket
import struct
import threading
import time
import traceback
class SocketServer:
def __init__(self):
self.server_host = '0.0.0.0'
self.server_port = 10001
self.socket_server = self.__listen()
self.conns = {}
self.__accept()
@staticmethod
def __set_reuse_addr(socket_obj):
"""
断开连接之后立马释放本地端口
Args:
socket_obj: socket对象
Returns: True or False
"""
socket_obj.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
return True
def __listen(self):
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.__set_reuse_addr(socket_server)
# 绑定IP/端口
socket_server.bind((self.server_host, self.server_port))
# 最多同时处理5个连接请求
socket_server.listen(5)
except:
print(traceback.format_exc())
finally:
return socket_server
def __disconnect(self, addr):
print('Disconnected, client={}'.format(addr))
self.__close(self.conns[addr])
self.conns.pop(addr)
return True
def __accept(self):
def accept():
while True:
try:
conn, addr = self.socket_server.accept()
print('Connection established, client={}'.format(addr))
self.__set_reuse_addr(conn)
self.conns[addr] = conn
threading.Thread(target=self.__recv, args=(addr, conn), daemon=True).start()
except:
print(traceback.format_exc())
threading.Thread(target=accept, daemon=True).start()
return True
def __recv(self, addr, client_socket, buff_size=1024):
while True:
try:
data_length = client_socket.recv(4)
# 读取data_length
if data_length:
data_length = struct.unpack('i', data_length)[0]
print('Recv from: {}, data_length: {}'.format(addr, data_length))
# 读取data
if data_length <= buff_size:
data = client_socket.recv(data_length)
else:
buff_size_ = buff_size
# 已接收到的size
total_recv_size = 0
data = b''
while total_recv_size < data_length:
recv_data = client_socket.recv(buff_size_)
data += recv_data
total_recv_size += len(recv_data)
left_size = data_length - total_recv_size
if left_size < buff_size:
buff_size_ = left_size
data = json.loads(data.decode('utf-8'))
print('Recv from: {}, data: {}'.format(addr, data))
else:
break
except:
print(traceback.format_exc())
break
self.__disconnect(addr)
return False
@staticmethod
def __close(socket_obj):
try:
socket_obj.close()
except:
print(traceback.format_exc())
return True
if '__main__' == __name__:
tcp_server = SocketServer()
while True:
time.sleep(3)