Browse Source

首次提交

Casper 11 months ago
parent
commit
f4ae8b4b1e
66 changed files with 6530 additions and 0 deletions
  1. 1 0
      .gitattributes
  2. 8 0
      .gitignore
  3. 92 0
      project-fastapi-hs/Dockerfile
  4. 29 0
      project-fastapi-hs/README-usage.bash
  5. 125 0
      project-fastapi-hs/README-usage.md
  6. 19 0
      project-fastapi-hs/README-usage.txt
  7. 208 0
      project-fastapi-hs/api/api.py
  8. 138 0
      project-fastapi-hs/api/token/v1.py
  9. 151 0
      project-fastapi-hs/api/v5/hs2000.py
  10. 154 0
      project-fastapi-hs/api/v5/hs3000.py
  11. 38 0
      project-fastapi-hs/api/v5/hs4000.py
  12. 12 0
      project-fastapi-hs/api/v5/hs5000.py
  13. 23 0
      project-fastapi-hs/app.py
  14. 22 0
      project-fastapi-hs/build.Dockerfile
  15. 37 0
      project-fastapi-hs/compose.yml
  16. 54 0
      project-fastapi-hs/cythonize.py
  17. 457 0
      project-fastapi-hs/data/SRI2024032814-4point.py
  18. 5 0
      project-fastapi-hs/data/SRI2024032814-4point.txt
  19. 13 0
      project-fastapi-hs/data/倒渣口坐标数据.json
  20. 20 0
      project-fastapi-hs/data/圆心坐标数据.json
  21. 440 0
      project-fastapi-hs/data/渣罐坐标数据.json
  22. 54 0
      project-fastapi-hs/default_data.py
  23. 84 0
      project-fastapi-hs/default_data_insert.py
  24. 38 0
      project-fastapi-hs/hub.py
  25. 10 0
      project-fastapi-hs/main.py
  26. 82 0
      project-fastapi-hs/main.spec
  27. 8 0
      project-fastapi-hs/run-c.sh
  28. 10 0
      project-fastapi-hs/run-exe-wrap.sh
  29. 6 0
      project-fastapi-hs/run-exe.sh
  30. 7 0
      project-fastapi-hs/run.sh
  31. 2 0
      project-fastapi-hs/test/HingeCarPath.msg
  32. 284 0
      project-fastapi-hs/test/SRI2024032514-机房端http接口文档.txt
  33. 32 0
      project-fastapi-hs/test/SRI2024032514-车端http接口文档.txt
  34. 8 0
      project-fastapi-hs/test/Segment.msg
  35. 14 0
      project-fastapi-hs/test/Segment.py
  36. 136 0
      project-fastapi-hs/test/test-2000.py
  37. 95 0
      project-fastapi-hs/test/test-3000.py
  38. 33 0
      project-fastapi-hs/test/test-4000.py
  39. 32 0
      project-fastapi-hs/test/test-5000.py
  40. 4 0
      project-fastapi-hs/test/test-enfei.py
  41. 30 0
      project-fastapi-hs/test/test_key_api.py
  42. 15 0
      project-fastapi-hs/test/test_sanyi.py
  43. 30 0
      project-fastapi-hs/test/test_token.py
  44. 264 0
      project-fastapi-hs/unit/Scheduler_a1.py
  45. 434 0
      project-fastapi-hs/unit/Scheduler_b1.py
  46. 604 0
      project-fastapi-hs/unit/Scheduler_c1.py
  47. 648 0
      project-fastapi-hs/unit/Scheduler_d1.py
  48. 47 0
      project-responder-hs/Dockerfile
  49. 12 0
      project-responder-hs/README-usage.bash
  50. 67 0
      project-responder-hs/api/Command.py
  51. 58 0
      project-responder-hs/api/Key.py
  52. 515 0
      project-responder-hs/api/TEST.py
  53. 222 0
      project-responder-hs/api/Task.py
  54. 36 0
      project-responder-hs/api/url.py
  55. 162 0
      project-responder-hs/app.py
  56. 37 0
      project-responder-hs/compose.yml
  57. 21 0
      project-responder-hs/hub.py
  58. 26 0
      project-responder-hs/main.py
  59. 5 0
      project-responder-hs/run.sh
  60. 25 0
      project-responder-hs/test/SRI2024032716-服务端mqtt话题接口文档.txt
  61. 11 0
      project-responder-hs/test/test_key_api.txt
  62. 31 0
      project-responder-hs/test/test_mqtt.py
  63. 41 0
      project-responder-hs/test/test_test_api.txt
  64. 50 0
      project-responder-hs/unit/JobManage.py
  65. 60 0
      project-responder-hs/unit/PotDataMessageListener.py
  66. 94 0
      project-responder-hs/unit/VehicleStateMessageListener.py

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+* text=auto eol=lf encoding=utf-8

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+.idea/
+*.pyc
+*.pyo
+*.log
+.venv/
+*/__pycache__
+.venv/*
+*.pth

+ 92 - 0
project-fastapi-hs/Dockerfile

@@ -0,0 +1,92 @@
+FROM ubuntu:20.04
+ENV DEBIAN_FRONTEND noninteractive
+
+RUN echo "Update apt:" \
+    && set -x \
+    && apt-get update \
+    && apt-get dist-upgrade -y \
+    && apt-get install -y \
+        wget unzip git apt-utils
+
+RUN echo "Install Python v3.8:" \
+    && set -x \
+#    && apt-get dist-upgrade -y \
+#    && apt-get update \
+    && apt-get install -y  \
+        python3-dev \
+        python3-pip \
+        python3-setuptools \
+        python3-wheel \
+    && pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \
+    && pip3 config set global.extra-index-url https://pypi.doubanio.com/simple \
+    && pip3 config set global.extra-index-url https://mirrors.163.com/pypi/simple \
+    && pip3 config set global.extra-index-url https://mirrors.cloud.tencent.com/pypi/simple \
+    && pip3 config set global.extra-index-url https://mirror.baidu.com/pypi/simple \
+    && pip3 install --upgrade --quiet pip setuptools \
+    && python3 --version
+
+RUN echo "Install OpenCV v4.2.0:" \
+    && set -x \
+    && apt-get update \
+    && apt-get install -y libopencv-dev python3-opencv \
+    && python3 -c "import cv2; print(cv2.__version__)"
+
+# --- set ffmpeg ---
+RUN apt-get install --no-install-recommends -y git ffmpeg supervisor
+
+# --- install requirements ---
+RUN echo "Install Python Requirements:" \
+    && pip3 install --default-timeout=1800 --no-cache-dir \
+        # --- for base --- \
+        cython==3.0.0a9 \
+        pyinstaller==4.10 \
+        # --- for libraries --- \
+        pycrypto==2.6.1 \
+        paramiko==2.7.2 \
+        apscheduler==3.7.0 \
+        # --- for client --- \
+        requests==2.25.1 \
+        redis==3.5.3 \
+        pymongo==3.11.2 \
+        influxdb==5.3.1 \
+        pymysql==0.9.3 \
+        peewee==3.17.0 \
+        SQLAlchemy==1.4.30\
+        # --- for server --- \
+        aiofiles==23.2.1 \
+        python-multipart==0.0.6 \
+        starlette==0.32.0 \
+        fastapi==0.108.0 \
+        fastapi-login==1.9.2 \
+        uvicorn==0.13.3 \
+        werkzeug==3.0.1 \
+        itsdangerous==1.1.0 \
+    && echo "End."
+
+        # --- for server --- \
+#        aiofiles==0.6.0 \
+#        python-multipart==0.0.5 \
+#        starlette==0.13.6 \
+#        fastapi==0.63.0 \
+#        fastapi-login==1.5.2 \
+#        supervisor==4.2.1 \
+#        uvicorn==0.13.3 \
+#        werkzeug==1.0.1 \
+#        itsdangerous==1.1.0 \
+
+
+#RUN echo "Install Torch v1.6.0:" \
+#    && pip3 install torch==1.6.0 torchvision==0.7.0 \
+#    && python3 -c "import torch; print(torch.__version__)"
+
+#RUN echo "Install Torch v1.9.0:" \
+#    && set -x \
+#    && apt-get install -y build-essential cmake \
+#    && pip3 install torch==1.9.0 torchvision==0.10.0 \
+#    && python3 -c "import torch; print(torch.__version__)"
+
+#COPY ./source/box.com/onnxruntime_gpu-1.10.0-cp38-cp38-linux_aarch64.whl /opt/onnxruntime_gpu-1.10.0-cp38-cp38-linux_aarch64.whl
+#RUN echo "Install onnxruntime:" \
+#    && pip3 install numpy==1.19.4 \
+#    && pip3 install /opt/onnxruntime_gpu-1.10.0-cp38-cp38-linux_aarch64.whl \
+#    && echo "End."

+ 29 - 0
project-fastapi-hs/README-usage.bash

@@ -0,0 +1,29 @@
+## NOTE
+
+# 一、构建操作
+# 调试
+echo "BEGIN:" \
+&& project_path="/home/ubuntu/repositories/repositories/casperz.py-project/project-fastapi-hs" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose.yml down \
+&& sudo docker-compose --file compose.yml up --detach --build \
+&& sudo docker exec -it sri-module-server-fastapi bash
+# 启动
+echo "BEGIN:" \
+&& project_path="/home/ubuntu/repositories/repositories/casperz.py-project/project-fastapi-hs" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose.yml down \
+&& sudo docker-compose --file compose.yml up --detach --build \
+&& sudo docker-compose --file compose.yml logs --follow
+# 强制构建并运行
+echo "BEGIN:" \
+&& project_path="/home/ubuntu/repositories/repositories/casperz.py-project/project-fastapi-hs" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose.yml down \
+&& sudo docker-compose --file compose.yml build --no-cache \
+&& sudo docker-compose --file compose.yml up --detach \
+&& sudo docker logs --follow sri-module-server-flask
+
+# 二、日常调试命令
+sudo chmod -R 777 /home/ubuntu/repositories/repositories
+sudo docker restart sri-module-hs01 && sudo docker logs -f sri-module-hs01

+ 125 - 0
project-fastapi-hs/README-usage.md

@@ -0,0 +1,125 @@
+#### usage
+```
+sudo docker restart fra-component-interface && sudo docker logs -f fra-component-interface
+```
+```
+# --- 试运行 ---
+echo "Test-Run-And-Check-Build:" \
+&& project_path="/home/server/projects/taiwuict/cscec-8bur-vms/component-interface" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose_test.yml down \
+&& sudo docker-compose --file compose_test.yml up --detach --build \
+&& sudo docker logs --follow test-component-interface
+```
+```
+# --- 试运行 ---
+echo "Test-Run-And-Check-Build:" \
+&& project_path="/home/server/projects/taiwuict/cscec-8bur-vms/component-interface" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose_test.yml down \
+&& sudo docker-compose --file compose_test.yml up --detach --build \
+&& sudo docker exec -it test-component-interface bash
+```
+```
+# --- 试运行 ---
+echo "Test-Run:" \
+&& project_path="/home/server/projects/taiwuict/cscec-8bur-vms/component-interface" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose_test.yml down \
+&& sudo docker-compose --file compose_test.yml up --detach \
+&& sudo docker-compose --file compose_test.yml logs --follow
+```
+```
+# --- 打包方式运行(压缩) ---
+echo "Wrap-Application:" \
+&& sudo docker rm -f fra-component-interface \
+&& sudo docker run \
+    --tty \
+    --env TZ=Asia/Shanghai \
+    --volume /home:/home \
+    --publish 8891:8000 \
+    --network node_network \
+    --name fra-component-interface \
+    fra-component-interface:u20 \
+    /bin/bash /home/server/projects/taiwuict/cscec-8bur-vms/component-interface/run-exe-wrap.sh
+```
+```
+# --- 打包方式运行(测试) ---
+echo "Run-Test:" \
+&& sudo docker rm -f fra-component-interface \
+&& sudo docker run \
+    --tty \
+    --env TZ=Asia/Shanghai \
+    --volume /home:/home \
+    --publish 8891:8000 \
+    --network node_network \
+    --name fra-component-interface \
+    ubuntu:20.04 \
+    /bin/bash /home/server/projects/taiwuict/cscec-8bur-vms/component-interface/run-exe.sh
+```
+```
+# --- 构建镜像并上传镜像仓库(构建) ---
+echo "step-1: 收集代码" \
+&& project_path="/home/server/projects/taiwuict/cscec-8bur-vms" \
+&& cd ${project_path} \
+&& sudo rm -rf build \
+&& sudo mkdir build \
+&& cd build \
+&& sudo cp -rf ../supplement-python . \
+&& sudo cp -rf ../component-interface . \
+&& sudo rm -rf component-interface/__pycache__ \
+&& sudo rm -rf component-interface/test \
+&& sudo rm -rf component-interface/*.tar \
+&& echo "step-2: 编译源码" \
+&& sudo docker run \
+    --tty \
+    --volume /home:/home \
+    fra-component-interface:u20 \
+    /bin/bash /home/server/projects/taiwuict/cscec-8bur-vms/build/component-interface/run-c.sh \
+&& echo "step-3: 制作镜像" \
+&& sudo docker build \
+    --tag fra-component-interface:new \
+    --file component-interface/build.Dockerfile \
+    --no-cache \
+    --rm \
+    . \
+&& echo "End."
+```
+```
+# --- 容器方式运行(测试) ---
+echo "Run-Test:" \
+&& sudo docker rm -f fra-component-interface \
+&& sudo docker run \
+    --detach \
+    --env TZ=Asia/Shanghai \
+    --volume /home:/home \
+    --publish 8891:8000 \
+    --network node_network \
+    --restart always \
+    --name fra-component-interface \
+    fra-component-interface:new \
+&& sudo docker logs -f fra-component-interface
+```
+```
+# --- 构建镜像并上传镜像仓库(上传) ---
+echo "Upload-Image:" \
+&& locale_name="fra-component-interface:new" \
+&& remote_name_1="casperz/fra-component-interface:arm64" \
+&& remote_name_2="casperz/fra-component-interface:amd64" \
+&& bash /home/server/resources/HostNecessities/2022/scripts/dockerhub_login.sh \
+&& sudo docker tag ${locale_name} ${remote_name_1} \
+&& sudo docker push ${remote_name_1}
+```
+```
+# --- 启动容器(下载) ---
+sudo docker rm -f fra-component-interface \
+&& sudo docker run \
+    --detach \
+    --env TZ=Asia/Shanghai \
+    --publish 8891:8000 \
+    --network node_network \
+    --restart always \
+    --name fra-component-interface \
+    casperz/fra-component-interface:arm64 \
+&& sudo docker logs -f fra-component-interface
+```

+ 19 - 0
project-fastapi-hs/README-usage.txt

@@ -0,0 +1,19 @@
+## NOTE
+
+# 一、构建镜像
+# 构建并运行
+echo "BEGIN:" \
+&& project_path="/home/server/repositories/repositories/sri-robot.cn/project.mccbts-ar-py/module-backend-fastapi" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose_test.yml down \
+&& sudo docker-compose --file compose_test.yml up --detach --build \
+&& sudo docker logs --follow sri-module-backend-fastapi
+
+# 二、日常调试命令
+sudo docker restart sri-module-backend-fastapi && sudo docker logs -f sri-module-backend-fastapi
+
+# 三、在win上运行命令
+# 1
+cd module-backend-fastapi
+# 2
+python main.py

+ 208 - 0
project-fastapi-hs/api/api.py

@@ -0,0 +1,208 @@
+from starlette.formparsers import MultiPartParser as StartletteMultiPartParser
+from fastapi import APIRouter, Request, Response, Depends
+from fastapi.responses import FileResponse
+from key import v1 as key_v1
+
+from hub import methods, Global
+import importlib
+
+router = APIRouter()
+
+config_dict = {
+
+    'v5': {
+
+        # --- 关于渣包车 ---
+        2001: 'v5.hs2000.code_2001',  # 获取全部渣包车状态接口
+        2002: 'v5.hs2000.code_2002',  # 指定渣包车点火操作接口
+        2003: 'v5.hs2000.code_2003',  # 指定渣包车熄火操作接口
+        2004: 'v5.hs2000.code_2004',  # 指定渣包车建立远程操作权限(或是切换)接口
+        # 2005: 'v5.hs2000.code_2005',  # 创建并执行自动驾驶任务【已停用】
+
+        2101: 'v5.hs2000.code_2101',  # 新增渣包车接口
+        2102: 'v5.hs2000.code_2102',  # 修改渣包车接口
+        2103: 'v5.hs2000.code_2103',  # 删除渣包车接口
+
+        # --- 关于关于任务 ---
+        3001: 'v5.hs3000.code_3001',  # 任务列表数据获取接口(分页)
+        # 3002: 'v5.hs3000.code_3002',  # 任务暂停接口【已停用】
+        3003: 'v5.hs3000.code_3003',  # 任务取消消接口
+        3004: 'v5.hs3000.code_3004',  # 任务创建并执行接口
+        3005: 'v5.hs3000.code_3005',  # 获取指定任务信息
+
+        # --- 关于场地数据 ---
+        4001: 'v5.hs4000.code_4001',  # 获取全部渣包状态数据接口
+        # 4002: 'v5.hs4000.code_4002',  # todo 获取接渣口状态数据
+        # 4003: 'v5.hs4000.code_4003',  # todo 获取倒渣口状态数据
+
+        # --- 关于告警 ---
+        5001: 'v5.hs5000.code_5001',  # 获取告警数据列表接口
+
+    }
+
+}
+
+methods_dict = {}  # {<tag>: {<method>: <path>}}
+
+
+def _get_method_by_code(code, tag):
+    """
+    通过code获取method(不调用不加载)
+    """
+    try:
+        # --- check ---
+        if tag not in methods_dict:
+            methods_dict[tag] = {}
+
+        # --- check ---
+        if code in methods_dict.get(tag):
+            return methods_dict.get(tag).get(code)
+
+        # --- get method ---
+        method_path = config_dict.get(tag).get(code)
+        tag, file_name, method_name = method_path.split('.')
+        script = importlib.import_module(f"api.{tag}.{file_name}")
+        method = getattr(script, method_name)
+
+        # --- set method ---
+        methods_dict[tag][code] = method
+        return method
+
+    except Exception as exception:
+        methods.debug_log('api._get_method_by_code.69', f"#exception: {exception.__class__.__name__}")
+        methods.debug_log('api._get_method_by_code.69', f"#traceback: {methods.trace_log()}")
+
+
+@router.post('/{tag}/api')
+async def post(tag: str, request: Request, response: Response, user: dict = Depends(key_v1.login_required)):
+    """"""
+
+    # --- check ---
+    if not user.get('skip_is'):
+        response.headers['authorization'] = key_v1.get_token_by_user(user)
+
+    # --- get sources ---
+    sources = await request.json()
+    # tag = sources.get('tag')
+    code = int(sources.get('code'))
+    page = sources.get('page', 1)
+    size = sources.get('size', 10)
+    ban_keys = ['code']
+
+    # --- set global args ---
+    sources = {key: val for key, val in sources.items() if key not in ban_keys}
+    sources['g_user_id'] = user.get('uid')
+    sources['g_user_name'] = user.get('username')
+    sources['g_user_pass'] = user.get('password')
+    sources['g_role_id'] = user.get('role_id')
+
+    # --- call action ---
+    try:
+        run_at = methods.now_ts()
+        method = _get_method_by_code(code=code, tag=tag)
+        result = await method(**sources)
+        methods.debug_log(f"api.post.101", f"#{tag}/{code}: use time {round(methods.now_ts() - run_at, 2)}s")
+
+        # --- check ---
+        if code in [
+            # 8201,  # 访客记录,列表
+        ] and result.__class__.__name__ == 'dict':
+            return dict(
+                code=result.get('code'),
+                data=result.get('data', [])[(page - 1) * size: page * size],
+                page=page,
+                size=size,
+                total=len(result.get('data', [])),
+            )
+        elif result.__class__.__name__ in ['FileResponse', 'dict']:
+            return result
+        else:
+            return dict(data=result, type=result.__class__.__name__)
+
+    except Exception as exception:
+
+        methods.debug_log('api.post.121', f"#tag: {tag}, #code: {code}")
+        methods.debug_log('api.post.121', f"#exception: {exception.__class__.__name__}")
+        methods.debug_log('api.post.121', f"#traceback: {methods.trace_log()}")
+        return dict(code=-1, data=[],
+                    reason=f"{exception.__class__.__name__}",
+                    detail=f"{methods.trace_log()}")
+
+
+@router.put('/actions')
+async def upload(request: Request, user: dict = Depends(key_v1.login_required)):
+    try:
+        # --- get sources ---
+        sources = dict()
+        parser = StartletteMultiPartParser(request.headers, request.stream())
+        params = await parser.parse()
+        # sources['params'] = params
+
+        # --- set sources --- fromdata列表处理
+        for key, value in params.multi_items():
+
+            # --- check list ---
+            if key[-4:] == 'list' and key not in sources:
+                sources[key] = list()
+            if key[-4:] == 'list':
+                sources[key].append(value)
+
+            # --- make list ---
+            else:
+                if key in sources:
+                    sources[key] = [sources[key]]
+                    sources[key].append(value)
+                else:
+                    sources[key] = value
+
+        # --- clean ---
+        tag = sources.get('tag', 'v1')
+        code = int(sources.get('code'))
+        del sources['tag']
+        del sources['code']
+
+        # --- set sources ---
+        sources['g_user_id'] = user.get('uid')
+        sources['g_user_name'] = user.get('username')
+        sources['g_user_pass'] = user.get('password')
+        sources['g_role_id'] = user.get('role_id')
+
+        # --- set files ---
+        files = dict()
+        for key, value in sources.items():
+            if value.__class__.__name__ == 'UploadFile':
+                files[key] = value
+        for key, value in files.items():
+            del sources[key]
+        sources['files'] = files
+
+        # --- call action ---
+        return await _get_method_by_code(code=code, tag=tag)(**sources)
+
+    except Exception as exception:
+
+        methods.debug_log('api.upload', f"m-299: exception | {exception.__class__.__name__}")
+        methods.debug_log('api.upload', f"m-299: traceback | {methods.trace_log()}")
+        return dict(code=-1,
+                    reason=f"{exception.__class__.__name__}",
+                    detail=f"{methods.trace_log()}")
+
+
+@router.get('/api')
+async def download(request: Request, response: Response, user: dict = Depends(key_v1.login_required)):
+    try:
+        parser = StartletteMultiPartParser(request.headers, request.stream())
+        params = await parser.parse()
+        instance_id = params.get('instance_id')
+        report = Global.mdb.get_one_by_id('', instance_id)
+        report_path = report.get('report') if report else ''
+        if not report_path:
+            return ''
+        return FileResponse(report_path)
+    except Exception as exception:
+
+        methods.debug_log('api.download.201', f"#exception: {exception.__class__.__name__}")
+        methods.debug_log('api.download.201', f"#traceback: {methods.trace_log()}")
+        return dict(code=-1,
+                    reason=f"{exception.__class__.__name__}",
+                    detail=f"{methods.trace_log()}")

+ 138 - 0
project-fastapi-hs/api/token/v1.py

@@ -0,0 +1,138 @@
+from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
+from werkzeug.security import check_password_hash
+from fastapi import APIRouter, Request, Response
+from fastapi import HTTPException, Header
+from fastapi.responses import JSONResponse
+from hub import methods, Global
+
+router = APIRouter()
+# mdb = Global.get_mongodb_client()
+# serializer = Serializer(secret_key='casper.com@2021', expires_in=86400 * 100)  # debug
+serializer = Serializer(secret_key='casper.com@2021', expires_in=86400)  # release
+
+
+def get_token_by_user(user):
+    """生成token"""
+    data = {
+        'id': user.get('uid'),
+        'username': user.get('username'),
+        'password': user.get('password'),
+    }
+    return serializer.dumps(data).decode('utf-8')
+
+
+@router.post('/token/api')
+async def get_token(request: Request, response: Response):
+    """获取令牌"""
+
+    methods.debug_log('api.get_token.28', f"#now at {methods.now_string()}, #ip: {request.client.host}")
+
+    # --- check key --- # debug
+    # node_api = NodeApi()
+    # if not node_api.verify_key():
+    # 	return JSONResponse(status_code=401, content=dict(message='not verified!', code=1))
+
+    # --- get params ---
+    params = await request.json()
+    username = params.get('username')
+    password = params.get('password')
+    user = Global.mdb.get_one('User', {'username': username})
+    if user:
+        role_name = Global.mdb.get_one_by_id('UserRole', user.get('role_id')).get('role_name')
+    else:
+        role_name = ''
+
+    # --- fail log---
+    if not user:
+        data = {
+            'username': username,
+            'is_login': 'Fail',
+            'role_name': role_name,
+            'login_at': methods.now_ts(),
+            'login_ip': request.client.host,
+        }
+        Global.mdb.add('UserLoginLog', data)
+        code = 2
+    elif not check_password_hash(user['password'], password):
+        data = {
+            'username': username,
+            'is_login': 'Fail',
+            'role_name': role_name,
+            'login_at': methods.now_ts(),
+            'login_ip': request.client.host,
+        }
+        Global.mdb.add('UserLoginLog', data)
+        code = 3
+    else:
+        data = {
+            'username': username,
+            'role_name': role_name,
+            'is_login': 'Pass',
+            'login_at': methods.now_ts(),
+            'login_ip': request.client.host,
+        }
+        Global.mdb.add('UserLoginLog', data)
+        code = 0
+
+    # --- 登录失败 ---
+    if code:
+        return JSONResponse(status_code=401, content=dict(message='unauthorized access!', code=code))
+
+    # --- make token ---
+    data = {
+        'id': str(user['_id']),
+        'username': user['username'],
+        'password': user['password'],
+    }
+    token = serializer.dumps(data).decode('utf-8')
+    content = dict(message='authorization passed.', uid=str(user['_id']), role_name=role_name, code=0)
+    headers = {'authorization': token}
+    return JSONResponse(content=content, headers=headers)
+
+
+async def login_required(request: Request):
+    """
+    检查登录token
+    methods.debug_log('token.login_required.96', f"#code: {code}")
+    """
+
+    # --- check ---
+    # if request.method == 'POST':
+    #     sources = await request.json()
+    #     tag = sources.get('tag', 'v1')
+    #     code = int(sources.get('code'))
+    #     # methods.debug_log('token.login_required', f"m-107: code -> {code} | token -> {token}")
+    #     if not token and tag == 'v3' and code in [1102, 8201]:
+    #         methods.debug_log('token.login_required', f"m-103: code -> {code}")
+    #         superuser = Global.mdb.get_one('User', {'username': 'admin'})
+    #         return {
+    #             'uid': str(superuser.get('_id')),
+    #             'username': 'admin',
+    #             'password': 'admin',
+    #             'role_id': superuser.get('role_id'),
+    #             'skip_is': True,
+    #         }
+
+    # --- check --- todo 屏蔽token验证,正常情况下应放开
+    # if not token:
+    #     # raise HTTPException(status_code=401, detail='unauthorized access!')
+    #     raise HTTPException(status_code=401, headers=dict(message='unauthorized access!', code='4'))
+
+    # --- fill --- todo 屏蔽token验证,正常情况下应放开
+    # try:
+    #     data = serializer.loads(token)
+    #     user = Global.mdb.get_one_by_id('User', data['id'])
+    #     role_id = user.get('role_id')
+    #     # role_acl = Global.mdb.get_one_by_id('UserRole', role_id).get('role_acl')
+    #     return {
+    #         'uid': data['id'],
+    #         'username': data['username'],
+    #         'password': data['password'],
+    #         'role_id': role_id,
+    #     }
+    # except Exception as e:
+    #     # raise HTTPException(status_code=401, detail='unauthorized access!')
+    #     raise HTTPException(status_code=401, headers=dict(message='unauthorized access!', code='5'))
+
+    # todo 正常情况下应屏蔽
+    return {'skip_is': True}

+ 151 - 0
project-fastapi-hs/api/v5/hs2000.py

@@ -0,0 +1,151 @@
+from hub import methods, Global
+
+
+async def code_2001(**sources):
+    """
+    获取全部渣包车状态
+    """
+    """
+    VehicleInfo: 渣包车信息表
+    VehicleInfo.uuid: 车辆标识
+    VehicleInfo.name: 车辆名称
+    VehicleInfo.address: 车辆ip
+    VehicleInfo.state: 车辆状态 1 离线(默认值) 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+    VehicleInfo.check_vehicle_direction: 当前车头方向是否符合自动驾驶条件 True 符合 False 不符合(默认值)
+    VehicleInfo.current_vehicle_direction: 当前车头方向类型 3 围栏方向 9 渣场方向 12 维修间方向 6 维修间正对方向 0 未知(默认值)
+    VehicleInfo.check_vehicle_coordinate: 当前车辆坐标是否符合自动驾驶条件 True 符合 False 不符合(默认值)
+    VehicleInfo.current_vehicle_coordinate_x: 当前所在位置坐标 
+    VehicleInfo.current_vehicle_coordinate_y: 当前所在位置坐标 
+    VehicleInfo.current_vehicle_weight: 当前车辆负载重量  
+    """
+    # --- fill d1 ---
+    d1 = list()
+    for item in Global.mdb.get_all('VehicleInfo'):
+        # --- check ---
+        if 'check_vehicle_direction' not in item:
+            item['check_vehicle_direction'] = False
+        if 'current_vehicle_direction' not in item:
+            item['current_vehicle_direction'] = 0
+        if 'state' not in item:
+            item['state'] = 1
+        if 'current_vehicle_weight' not in item:
+            item['current_vehicle_weight'] = 0
+
+        # --- append ---
+        item['uuid'] = str(item.get('_id'))
+        item.pop('_id')
+        d1.append(item)
+
+    return dict(code=0, data=d1)
+
+
+async def code_2002(**sources):
+    """
+    指定渣包车点火操作接口
+    """
+    return dict(code=0, data=sources.get('uuid'))
+
+
+async def code_2003(**sources):
+    """
+    指定渣包车熄火操作接口
+    """
+    return dict(code=0, data=sources.get('uuid'))
+
+
+async def code_2004(**sources):
+    """
+    指定渣包车建立远程操作权限(或是切换)接口
+    """
+    return dict(code=0, data=sources.get('uuid'))
+
+
+async def code_2101(**sources):
+    """
+    新增渣包车接口
+    """
+    # --- save ---
+    """
+    VehicleInfo: 渣包车信息表
+    VehicleInfo.uuid: 标识
+    VehicleInfo.name: 车辆名称
+    VehicleInfo.address: 车辆ip
+    VehicleInfo.state: 车辆状态 1 离线(默认值) 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+    VehicleInfo.check_vehicle_direction: 当前车头方向是否符合自动驾驶条件 True 符合 False 不符合(默认值)
+    VehicleInfo.current_vehicle_direction: 当前车头方向类型 3 围栏方向 9 渣场方向 12 维修间方向 6 维修间正对方向 0 未知(默认值)
+    VehicleInfo.check_vehicle_coordinate: 当前车辆坐标是否符合自动驾驶条件 True 符合 False 不符合(默认值)
+    VehicleInfo.current_vehicle_coordinate_x: 当前所在位置坐标 
+    VehicleInfo.current_vehicle_coordinate_y: 当前所在位置坐标 
+    VehicleInfo.current_vehicle_weight: 当前车辆负载重量 
+    """
+    data = {
+        'name': sources.get('name'),
+        'address': sources.get('address'),
+        'state': 1,
+        'check_vehicle_direction': False,
+        'update_at': methods.now_ts(),
+    }
+    uuid = Global.mdb.add('VehicleInfo', data)
+    return dict(code=0, data=uuid)
+
+
+async def code_2102(**sources):
+    """
+    修改渣包车接口
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, detail=f"something is wrong.")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('VehicleInfo', uuid)
+    if not item:
+        return dict(code=2, detail=f"something is wrong.")
+
+    # --- update VehicleInfo by id ---
+    """
+    VehicleInfo: 渣包车信息表
+    VehicleInfo.uuid: 标识
+    VehicleInfo.name: 车辆名称
+    VehicleInfo.address: 车辆ip
+    VehicleInfo.state: 车辆状态 1 离线(默认值) 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+    VehicleInfo.check_vehicle_direction: 当前车头方向是否符合自动驾驶条件 True 符合 False 不符合(默认值)
+    VehicleInfo.current_vehicle_direction: 当前车头方向类型 3 围栏方向 9 渣场方向 12 维修间方向 6 维修间正对方向 0 未知(默认值)
+    VehicleInfo.check_vehicle_coordinate: 当前车辆坐标是否符合自动驾驶条件 True 符合 False 不符合(默认值)
+    VehicleInfo.current_vehicle_coordinate_x: 当前所在位置坐标 
+    VehicleInfo.current_vehicle_coordinate_y: 当前所在位置坐标 
+    VehicleInfo.current_vehicle_weight: 当前车辆负载重量 
+    """
+    update_dict = dict()
+    update_dict['update_at'] = methods.now_ts()
+    if sources.get('name'):
+        update_dict['name'] = sources.get('name')
+    if sources.get('address'):
+        update_dict['address'] = sources.get('address')
+    Global.mdb.update_one_by_id('VehicleInfo', uuid, update_dict)
+
+    # --- get ---
+    item = Global.mdb.get_one_by_id('VehicleInfo', uuid)
+    item['uuid'] = str(item.get('_id'))
+    item.pop('_id')
+    return dict(code=0, data=item)
+
+
+async def code_2103(**sources):
+    """
+    删除渣包车接口
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, detail=f"something is wrong.")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('VehicleInfo', uuid)
+    if not item:
+        return dict(code=2, detail=f"something is wrong.")
+
+    # --- clean ---
+    Global.mdb.remove_one_by_id('VehicleInfo', uuid)
+    return dict(code=0, data=uuid)

+ 154 - 0
project-fastapi-hs/api/v5/hs3000.py

@@ -0,0 +1,154 @@
+from hub import methods, Global
+
+
+async def code_3001(**sources):
+    """
+    任务列表数据获取接口(分页)
+    """
+    # --- check ---
+    if not sources.get('page'):
+        return dict(code=1, detail=f"something is wrong.")
+    elif not sources.get('size'):
+        return dict(code=2, detail=f"something is wrong.")
+
+    # --- fill d1 ---
+    d1 = list()
+    page = sources.get('page')
+    size = sources.get('size')
+    for item in Global.mdb.get_all('VehicleTaskList'):
+        item['uuid'] = str(item.get('_id'))
+        item.pop('_id')
+        d1.append(item)
+    return dict(code=0, data=d1[(page - 1) * size: page * size], total=len(d1), page=page, size=size)
+
+
+async def code_3002(**sources):
+    """
+    任务暂停接口
+    """
+    return dict(code=0, data=sources.get('uuid'))
+
+
+async def code_3003(**sources):
+    """
+    任务取消接口
+    """
+    return dict(code=0, data=sources.get('uuid'))
+
+
+async def code_3004(**sources):
+    """
+    任务创建并执行
+    渣罐位:
+        X排.X位: 渣罐X排X位
+    接渣口:
+        dump.MN: 接渣口7
+        dump.KL: 接渣口6
+        dump.IJ: 接渣口5
+        dump.GH: 接渣口4
+        dump.EF: 接渣口3
+        dump.CD: 接渣口2
+        dump.AB: 接渣口1
+    接渣口:
+        load.1
+        load.2
+        load.3
+    """
+    # --- check ---
+    if not sources.get('uuid'):
+        return dict(code=1, detail=f"Reason: 参数缺失")
+    elif not sources.get('task_type'):
+        return dict(code=2, detail=f"Reason: 参数缺失")
+    elif not sources.get('target_point_name'):
+        return dict(code=3, detail=f"Reason: 参数缺失")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('VehicleInfo', sources.get('uuid'))
+    if not item:
+        return dict(code=4, detail=f"Reason: 查询为空")
+
+    # --- check ---
+    # if not item.get('current_vehicle_direction'):
+    #     return dict(code=5, detail=f"Reason: 参数缺失")
+
+    # --- check ---
+    # if not item.get('current_vehicle_direction') in [3, 9, 6, 12]:
+    #     return dict(code=6, detail=f"Reason: 不具备启动条件")
+
+    # --- check ---
+    if not item.get('state') or item.get('state') != 2:
+        return dict(code=7, detail=f"Reason: 状态错误")
+
+    # --- get navigation ---
+    from unit.Scheduler_d1 import test
+    navigation = test(current_direction_type=item.get('current_vehicle_direction'),
+                      start_x=item.get('coordinate_x'),
+                      start_y=item.get('coordinate_y'),
+                      target_point_name=sources.get('target_point_name'))
+
+    # --- send --- todo 拼接数据,发送给车端
+    methods.debug_log('hs3000.code_3004.86:', f"#navigation: {navigation}")
+    # Global.http_api.cmd1001(address=item.get('address'), navigation=navigation)
+
+    # --- save ---
+    """
+    VehicleTaskList: 渣包车自动驾驶任务信息表
+    VehicleTaskList.uuid: 任务id
+    VehicleTaskList.vehicle_uuid: 车辆id
+    VehicleTaskList.task_type: 任务类型 101 自动驾驶 102 叉包 103 放包 104 倒渣
+    VehicleTaskList.target_point_name: 目标点名称
+    VehicleTaskList.task_state: 任务状态 1 已经下发 2 已完成 3 中止 4 失败
+    VehicleTaskList.create_at: 创建时间
+    """
+    data = {
+        'vehicle_uuid': sources.get('uuid'),
+        'task_type': sources.get('task_type'),
+        'target_point_name': sources.get('target_point_name'),
+        'task_state': 1,  # 任务状态 1 已经下发 2 已完成 3 中止 4 失败
+        'create_at': methods.now_ts(),
+    }
+    uuid = Global.mdb.add('VehicleTaskList', data)
+    return dict(code=0, data=uuid)
+
+
+async def code_3005(**sources):
+    """
+    获取指定任务信息
+    """
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, detail=f"Reason: 参数缺失")
+
+    item = Global.mdb.get_one_by_id('VehicleTaskList', uuid)
+    if not item:
+        return dict(code=2, detail=f"Reason: 查无数据")
+
+    # --- check ---
+    vehicle_uuid = item.get('vehicle_uuid')
+    vehicle = Global.mdb.get_one_by_id('VehicleInfo', vehicle_uuid)
+    if not vehicle:
+        return dict(code=3, detail=f"Reason: 查无数据")
+
+    # --- check ---
+    state = vehicle.get('state')
+    if not state:
+        return dict(code=4, detail=f"Reason: 参数缺失")
+
+    # --- check --- todo 需要改成socket通信方式,接收车端消息来更新任务状态
+    """
+    VehicleTaskList: 渣包车自动驾驶任务信息表
+    VehicleTaskList.uuid: 任务id
+    VehicleTaskList.vehicle_uuid: 车辆id
+    VehicleTaskList.task_type: 任务类型 101 自动驾驶 102 叉包 103 放包 104 倒渣
+    VehicleTaskList.target_point_name: 目标点名称
+    VehicleTaskList.task_state: 任务状态 1 已经下发 2 已完成 3 中止 4 失败
+    VehicleTaskList.create_at: 创建时间
+    """
+    if state == 2:
+        Global.mdb.update_one_by_id('VehicleTaskList', uuid, {'task_state': 2})
+
+    # --- fill ---
+    item.pop('_id')
+    item['uuid'] = uuid
+    item['task_state'] = 2
+    return dict(code=0, data=item)

+ 38 - 0
project-fastapi-hs/api/v5/hs4000.py

@@ -0,0 +1,38 @@
+from hub import methods, Global
+
+
+async def code_4001(**sources):
+    """
+    获取全部渣包状态数据接口
+    """
+    # --- fill d1 ---
+    d1 = Global.enfei_api.get_pot_list()
+
+    # --- check ----
+    if type(d1) != list:
+        reason, detail = d1
+        return dict(code=1, data=[], reason=reason, detail=detail)
+
+    # --- fill d2 ---
+    d2 = list()
+    d3 = list(reversed(d1))
+    d4 = list(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'])  # 排
+    d5 = list(range(31))  # 罐数
+    count = 0
+    for i in d4:
+        for j in d5:
+            # --- define ---
+            data = {
+                'pot_name': f"{i}.{j + 1}",  # 渣罐别称
+                'pot_status': 0,  # 渣罐状态 1 空位 2 就绪 3 缓冷(空冷) 4 水冷 5 自冷(水冷) 6 待倒 7 故障
+                'pot_number': '',  # 渣罐编号
+            }
+            # --- update ---
+            # methods.debug_log('hs4000.code_4001.48:', f"#count: {count}")
+            data['pot_status'] = d3[count].get('packageStatus')
+            data['pot_number'] = d3[count].get('localNumber')
+            d2.append(data)
+            # methods.debug_log('hs4000.code_4001.52:', f"#data: {data}")
+            count += 1
+
+    return dict(code=0, data=d2)

+ 12 - 0
project-fastapi-hs/api/v5/hs5000.py

@@ -0,0 +1,12 @@
+from hub import methods, Global
+
+
+async def code_5001(**sources):
+    """
+    获取告警数据列表接口
+    1.2023.12.13 08:48 1号车油量低 请及时加油!
+    """
+    data = [
+        {'message': '1号车油量低,请及时加油!', 'create_at': 1704435199}
+    ]
+    return dict(code=0, data=data)

+ 23 - 0
project-fastapi-hs/app.py

@@ -0,0 +1,23 @@
+"""
+注册路由
+"""
+from starlette.middleware.sessions import SessionMiddleware
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi import FastAPI, Depends
+from api.api import router as api_router
+from api.token.v1 import login_required, router as key_router
+
+app = FastAPI()
+app.add_middleware(SessionMiddleware, secret_key='casper.com@2021')
+app.include_router(key_router)
+app.include_router(api_router, dependencies=[Depends(login_required)],
+                   responses={404: {'description': 'Not found!'}})
+
+# --- 支持跨域访问 --- see: https://blog.csdn.net/qq_33801641/article/details/120540963
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],  # 允许访问的源
+    allow_credentials=True,  # 支持 cookie
+    allow_methods=["*"],  # 允许使用的请求方法
+    allow_headers=["*"],  # 允许携带的 Headers
+)

+ 22 - 0
project-fastapi-hs/build.Dockerfile

@@ -0,0 +1,22 @@
+FROM ubuntu:20.04
+ENV DEBIAN_FRONTEND noninteractive
+
+RUN echo "Support Time Zone" \
+    && set -x \
+    && apt-get dist-upgrade  \
+    && apt-get update \
+    && apt-get install -y tzdata
+
+# --- put file ---
+COPY ./component-interface /opt/cscec-8bur-vms/component-interface
+COPY ./supplement-python /opt/cscec-8bur-vms/supplement-python
+
+# --- clean ---
+RUN rm -rf /opt/cscec-8bur-vms/component-interface/*Dockerfile
+RUN rm -rf /opt/cscec-8bur-vms/component-interface/*spec
+RUN rm -rf /opt/cscec-8bur-vms/component-interface/*md
+RUN rm -rf /opt/cscec-8bur-vms/component-interface/*sh
+
+# --- run file ---
+WORKDIR /opt/cscec-8bur-vms/component-interface
+CMD ["/bin/bash", "-c", "./main"]

+ 37 - 0
project-fastapi-hs/compose.yml

@@ -0,0 +1,37 @@
+version: '3.5'
+services:
+
+    sri-module-hs01:
+
+        # --- building ---
+        image: sri-module-hs01:2024
+        build:
+            context: ./
+            dockerfile: ./Dockerfile
+        environment:
+            TZ: Asia/Shanghai
+
+        # --- binding ---
+        volumes:
+            - /home:/home
+        networks:
+            - sri_network
+        ports:
+            - "9000:9000"
+
+        # --- running ---
+        container_name: sri-module-hs01
+
+        # --- for debug ---
+#        working_dir: /home/ubuntu/repositories/repositories/casperz.py-project/project-fastapi-hs
+#        stdin_open: true
+#        tty: true
+
+        # --- for release ---
+        working_dir: /home/ubuntu/repositories/repositories/casperz.py-project/project-fastapi-hs
+        command: bash run.sh
+        restart: always
+
+networks:
+    sri_network:
+        external: true

+ 54 - 0
project-fastapi-hs/cythonize.py

@@ -0,0 +1,54 @@
+"""
+用于python源码打包成so文件,python解释器运行main.py
+"""
+from distutils.core import setup
+from Cython.Build import cythonize
+import os
+
+py_dir = [
+    '/home/server/projects/taiwuict/cscec-8bur-vms/build/component-interface',
+    '/home/server/projects/taiwuict/cscec-8bur-vms/build/supplement-python',
+]
+
+
+def get_file_path_list(dir_path, path_list=None):
+    if not path_list:
+        path_list = []
+    for path, folders, files in os.walk(dir_path):
+
+        for file_name in files:
+            if file_name[-2:] != 'py':
+                continue
+            if 'cythonize' in file_name:
+                continue
+            # if '__init__' in file_name:
+            #     continue
+            file_path = os.path.join(path, file_name)
+            if file_path in path_list:
+                continue
+            path_list.append(file_path)
+
+        for folder_name in folders:
+            get_file_path_list(os.path.join(path, folder_name), path_list)
+
+    return path_list
+
+
+py_list = []
+for dir in py_dir:
+    py_list += get_file_path_list(dir)
+print(py_list)
+
+# setup(ext_modules=cythonize(module_list=py_list,
+#                             compiler_directives=dict(c_string_encoding="utf-8", language_level=3)))
+
+# --- one by one ---
+for py_file in py_list:
+    setup(
+        name='fra',
+        package_dir={
+            'v3': '',
+            'base_original': '',
+        },
+        ext_modules=cythonize(module_list=[py_file],
+                              compiler_directives=dict(c_string_encoding="utf-8", language_level=3)))

+ 457 - 0
project-fastapi-hs/data/SRI2024032814-4point.py

@@ -0,0 +1,457 @@
+"""
+大 小 英文 中文 常用指代意义
+Α α alpha 阿尔法 角度、系数、角加速度、第一个、电离度、转化率
+Β β beta 贝塔 磁通系数、角度、系数
+Γ γ gamma 伽玛 电导系数、角度、比热容比
+Δ δ delta 得尔塔 变化量、焓变、熵变、屈光度、一元二次方程中的判别式、化学位移
+Ε ε epsilon 艾普西隆 对数之基数、介电常数、电容率
+Ζ ζ zeta 泽塔 系数、方位角、阻抗、相对黏度
+Η η eta 伊塔 迟滞系数、机械效率
+Θ θ theta 西塔 温度、角度
+Ι ι iota 约(yāo)塔 微小、一点
+Κ κ kappa 卡帕 介质常数、绝热指数
+∧ λ lambda 拉姆达 波长、体积、导热系数 普朗克常数
+Μ μ mu 谬 磁导率、微、动摩擦系(因)数、流体动力黏度、货币单位,莫比乌斯函数
+Ν ν nu 纽 磁阻系数、流体运动粘度、光波频率、化学计量数
+Ξ ξ xi 克西 随机变量、(小)区间内的一个未知特定值
+Ο ο omicron 奥米克戎 高阶无穷小函数
+∏ π pi 派 圆周率、π(n)表示不大于n的质数个数、连乘
+Ρ ρ rho 柔 电阻率、柱坐标和极坐标中的极径、密度、曲率半径
+∑ σ,ς sigma 西格马 总和、表面密度、跨导、正应力、电导率
+Τ τ tau 陶 时间常数、切应力、2π(两倍圆周率)
+Υ υ upsilon 阿普西龙 位移
+Φ φ phi 斐 磁通量、电通量、角、透镜焦度、热流量、电势、直径、空集、欧拉函数
+Χ χ chi 希 统计学中有卡方(χ^2)分布
+Ψ ψ psi 普西 角速、介质电通量、ψ函数、磁链
+Ω ω omega 欧米伽 欧姆、角速度、角频率、交流电的电角度、化学中的质量分数、不饱和度
+"""
+import math
+import numpy
+import json
+
+# 道路名称列表
+path_name_list = [
+    'MN',
+    'KL',
+    'IJ',
+    'GH',
+    'EF',
+    'CD',
+    'AB',
+]
+
+
+def get_path_name_by_pot_name(pot_name):
+    """
+    根据罐名获取道路名称
+    """
+    s1, s2 = pot_name.split('.')
+    for item in path_name_list:
+        if s1 in item:
+            return item
+
+
+def get_two_end_point_by_theta_and_delta(start_point=(1, 1), theta=45, delta=1.1):
+    """
+    获取指定偏移量的两个点
+    """
+    start_point_x = start_point[0]
+    start_point_y = start_point[1]
+    theta = degrees_to_radians(theta)
+    x1 = start_point_x + (delta * numpy.cos(theta))
+    y1 = start_point_y + (delta * numpy.sin(theta))
+    x2 = start_point_x - (delta * numpy.cos(theta))
+    y2 = start_point_y - (delta * numpy.sin(theta))
+    # print(f"dubug.136: p1: {(x1, y1)}, p2: {(x2, y2)}")
+    return (x1, y1), (x2, y2)
+
+
+def get_distance_by_two_point(A=(1, 1), B=(1, 1)):
+    """
+    求两点之间的距离
+    """
+    A_x = float(A[0])
+    A_y = float(A[1])
+    B_x = float(B[0])
+    B_y = float(B[1])
+    distance = ((A_x - B_x) ** 2 + ((A_y - B_y) ** 2)) ** (1 / 2)
+    # print(f"dubug.53: 点{A}、点{B},距离为{s}")
+    return distance
+
+
+# def get_point_by_k_and_s(k=3, s=10):
+#     """
+#     已知一个点坐标,和一条线斜率,求顺着这条线指定距离后的坐标
+#     """
+#     # 计算倾斜角度 theta | θ = arctan(k)
+#     theta = numpy.arctan(k)
+#
+#     # 计算新点坐标 (x2, y2)
+#     x2 = x1 + (s * math.cos(theta))
+#     y2 = y1 + (s * math.sin(theta))
+#     # print(f"dubug.67: 增加{s}单位长度后的新点坐标 ({x2}, {y2})")
+#     return x2, y2
+
+def get_a_and_b_by_c_and_min_theta(c=13.7, min_theta=1):
+    """
+    根据直角三角形最小角和斜边,计算直角三角形直角边a、b
+    """
+    a = c * numpy.sin(degrees_to_radians(min_theta))
+    b = c * numpy.cos(degrees_to_radians(min_theta))
+    return a, b
+
+
+# def calc_theta():
+#     """
+#     计算倾斜角度
+#     """
+#     import math
+#
+#     # 已知函数 y = 3x + 2
+#     def f(x):
+#         return 3 * x + 2
+#
+#     # 已知点的坐标 (x1, y1)
+#     x1 = 1
+#     y1 = f(x1)
+#
+#     # 计算斜率 k
+#     k = 3
+#
+#     # 计算倾斜角度 theta | θ = arctan(k)
+#     theta = math.atan(k)
+#
+#     # 计算新点坐标 (x2, y2)
+#     delta_x = 10 * math.cos(theta)
+#     delta_y = 10 * math.sin(theta)
+#
+#     x2 = x1 + delta_x
+#     y2 = y1 + delta_y
+#
+#     print("已知点坐标 (x1, y1) =", (x1, y1))
+#     print("增加 10 个单位长度后的新点坐标 (x2, y2) =", (x2, y2))
+
+
+def get_k_and_b_by_points(A, B):
+    """
+    计算k与b
+    pip install sympy==1.12
+    """
+    x1 = A[0]
+    y1 = A[1]
+    x2 = B[0]
+    y2 = B[1]
+
+    from sympy import symbols, Eq, solve, Symbol
+    k = Symbol('k')
+    b = Symbol('b')
+    eqs = [Eq(k * x1 + b, y1), Eq(k * x2 + b, y2)]
+    result = solve(eqs, [k, b])
+    k = result.get(k)
+    b = result.get(b)
+    # print(f"dubug.119: y = {k}x + {b}")
+    return k, b
+
+
+def get_theta_by_k(k):
+    """
+    根据斜率计算夹角
+    """
+    theta = numpy.arctan(float(k))
+    # print(f"dubug.128: theta: {radians_to_degrees(theta)}")
+    return theta
+
+
+def degrees_to_radians(degrees):
+    """角度转弧度"""
+    return numpy.radians(degrees)
+
+
+def radians_to_degrees(radians):
+    """弧度转角度"""
+    return numpy.degrees(radians)
+
+
+def get_pi():
+    """圆周率"""
+    return numpy.pi
+
+
+def get_radians_for_90():
+    """获取90度对应的弧度"""
+    return numpy.pi / 2
+
+
+def main(MN01, MN31, A01, A31):
+    """
+    """
+    # --- fill v1 巷道间隔距离 ---
+    distance = get_distance_by_two_point(A01, MN01)
+    v1 = distance / 7
+    # print(f"debug.164: 巷道间隔距离: {v1}")
+
+    # --- fill v2 坐标系偏移角度 ---
+    k, b = get_k_and_b_by_points(A01, MN01)
+    v2 = get_theta_by_k(k)
+    v2 = radians_to_degrees(v2)
+    print(f"debug.164: 坐标系偏移角度: {90-v2}")
+
+    # --- fill d1 ---
+    _, KL01 = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v1)
+    _, IJ01 = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v1 * 2)
+    _, GH01 = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v1 * 3)
+    _, EF01 = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v1 * 4)
+    _, CD01 = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v1 * 5)
+    _, AB01 = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v1 * 6)
+    # print(f"debug.164: MN01: {MN01}")
+    # print(f"debug.164: KL01: {KL01}")
+    # print(f"debug.164: IJ01: {IJ01}")
+    # print(f"debug.164: GH01: {GH01}")
+    # print(f"debug.164: EF01: {EF01}")
+    # print(f"debug.164: CD01: {CD01}")
+    # print(f"debug.164: AB01: {AB01}")
+    # print(f"debug.164: A01: {A01}")
+    d1 = {
+        'MN.1': MN01,
+        'KL.1': KL01,
+        'IJ.1': IJ01,
+        'GH.1': GH01,
+        'EF.1': EF01,
+        'CD.1': CD01,
+        'AB.1': AB01,
+    }
+
+    # --- fill v3 巷道间隔距离 ---
+    distance = get_distance_by_two_point(A31, MN31)
+    v3 = distance / 7
+    # print(f"debug.188: 巷道间隔距离: {v3}")
+
+    # --- fill v4 坐标系偏移角度 ---
+    k, b = get_k_and_b_by_points(A31, MN31)
+    v4 = get_theta_by_k(k)
+    v4 = radians_to_degrees(v4)
+    print(f"debug.194: 坐标系偏移角度: {90-v4}")
+
+    # --- fill d2 ---
+    _, KL31 = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v3)
+    _, IJ31 = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v3 * 2)
+    _, GH31 = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v3 * 3)
+    _, EF31 = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v3 * 4)
+    _, CD31 = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v3 * 5)
+    _, AB31 = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v3 * 6)
+    # print(f"debug.233: MN31: {MN31}")
+    # print(f"debug.233: KL31: {KL31}")
+    # print(f"debug.233: IJ31: {IJ31}")
+    # print(f"debug.233: GH31: {GH31}")
+    # print(f"debug.233: EF31: {EF31}")
+    # print(f"debug.233: CD31: {CD31}")
+    # print(f"debug.233: AB31: {AB31}")
+    # print(f"debug.233: A31: {A31}")
+    d2 = {
+        'MN.31': MN31,
+        'KL.31': KL31,
+        'IJ.31': IJ31,
+        'GH.31': GH31,
+        'EF.31': EF31,
+        'CD.31': CD31,
+        'AB.31': AB31,
+    }
+
+    # --- fill pot_point_list ---
+    pot_point_list = list()
+    for i in list(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N']):
+        for j in list(range(31)):
+            # --- set ---
+            pot_name = f"{i}.{j + 1}"  # 罐位名称
+            path_name = get_path_name_by_pot_name(pot_name)
+            XX01 = d1.get(f"{path_name}.1")
+            XX31 = d2.get(f"{path_name}.31")
+
+            # --- get v5 ---
+            distance = get_distance_by_two_point(XX01, XX31)
+            v5 = distance / 30
+            # --- get v6 ---
+            k, b = get_k_and_b_by_points(XX01, XX31)
+            v6 = radians_to_degrees(get_theta_by_k(k))
+            # --- get road_center ---
+            road_center, _ = get_two_end_point_by_theta_and_delta(start_point=XX01, theta=v6, delta=v5 * (j + 1 - 1))
+            road_center_x = road_center[0]  # 道路中心坐标
+            road_center_y = road_center[1]  # 道路中心坐标
+
+            # --- update ---
+            data_string = f"{pot_name}|{0}|{0}|{road_center_x}|{road_center_y}"
+            # print(data_string)
+            pot_point_list.append(data_string)
+
+    # --- check ---
+    print(f"debug.300: total: {len(pot_point_list)}")
+    if len(pot_point_list) != 434:
+        raise "Error: 数据不全"
+
+    # --- fill json_data_dict ---
+    json_data_dict = {
+        "title": "渣罐坐标数据",
+        "pot_name|pot_x|pot_y|road_center_x|road_center_y": pot_point_list,
+        "total": len(pot_point_list),
+    }
+
+    # --- save 渣罐坐标数据 ---
+    with open('渣罐坐标数据.json', "w", encoding='utf-8') as f:
+        json.dump(json_data_dict, f, indent=4, sort_keys=True, ensure_ascii=False)
+
+    # --- get v7 转弯半径 ---
+    distance = get_distance_by_two_point(A01, MN01)
+    v7 = distance / 14
+    # print(f"debug.309: 转弯半径: {v7}")
+
+    # --- fill d5 ---
+    _, LMhead = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v7)
+    _, JKhead = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v7 * 3)
+    _, HIhead = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v7 * 5)
+    _, FGhead = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v7 * 7)
+    _, DEhead = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v7 * 9)
+    _, BChead = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v7 * 11)
+    _, Ahead = get_two_end_point_by_theta_and_delta(start_point=MN01, theta=v2, delta=v7 * 13)
+    # print(f"debug.327: LMhead: {LMhead}")
+    # print(f"debug.327: JKhead: {JKhead}")
+    # print(f"debug.327: HIhead: {HIhead}")
+    # print(f"debug.327: FGhead: {FGhead}")
+    # print(f"debug.327: DEhead: {DEhead}")
+    # print(f"debug.327: BChead: {BChead}")
+    # print(f"debug.327: Ahead: {Ahead}")
+    d5 = {
+        'LM.head': LMhead,
+        'JK.head': JKhead,
+        'HI.head': HIhead,
+        'FG.head': FGhead,
+        'DE.head': DEhead,
+        'BC.head': BChead,
+        'A.head': Ahead,
+    }
+    # print(f"debug.355: 转弯半径: {d5}")
+
+    # --- test ---
+    # print(f"debug.339: 最小角度: {90 - v2}")
+    # print(f"debug.339: 斜边 c: {v7}")
+    # short, long = get_a_and_b_by_c_and_min_theta(v7, 90 - v2)
+    # print(f"debug.339: 直角边 a: {short}")
+    # print(f"debug.339: 直角边 b: {long}")
+
+    # --- fill centers_list ---
+    centers_list = list()
+    for k, v in d5.items():
+        # --- set ---
+        center_x, center_y = v
+        short, long = get_a_and_b_by_c_and_min_theta(v7, 90 - v2)
+        data_string = f"{k}|{center_x}|{center_y}"
+
+        # --- get center_3_x center_3_y --- 顺时针偏移一定角度
+        center_3_x = center_x + long
+        center_3_y = center_y - short
+        data_string += f"|{center_3_x}|{center_3_y}"
+
+        # --- get center_9_x center_9_y --- 顺时针偏移一定角度
+        center_9_x = center_x - long
+        center_9_y = center_y + short
+        data_string += f"|{center_9_x}|{center_9_y}"
+
+        # --- get center_12_x center_12_y --- 顺时针偏移一定角度
+        center_12_x = center_x + short
+        center_12_y = center_y + long
+        data_string += f"|{center_12_x}|{center_12_y}"
+
+        # --- get center_6_x center_6_y --- 顺时针偏移一定角度
+        center_6_x = center_x - short
+        center_6_y = center_y - long
+        data_string += f"|{center_6_x}|{center_6_y}"
+
+        # --- append ---
+        centers_list.append(data_string)
+
+    # --- get v201 转弯半径 ---
+    distance = get_distance_by_two_point(A31, MN31)
+    v201 = distance / 14
+    print(f"debug.309: 转弯半径: {v201}")
+
+    # --- fill tail_center_dict ---
+    _, LMtail = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v201)
+    _, JKtail = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v201 * 3)
+    _, HItail = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v201 * 5)
+    _, FGtail = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v201 * 7)
+    _, DEtail = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v201 * 9)
+    _, BCtail = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v201 * 11)
+    _, Atail = get_two_end_point_by_theta_and_delta(start_point=MN31, theta=v4, delta=v201 * 13)
+    # print(f"debug.397: LMtail: {LMtail}")
+    # print(f"debug.397: MN31: {MN31}")
+    tail_center_dict = {
+        'LM.tail': LMtail,
+        'JK.tail': JKtail,
+        'HI.tail': HItail,
+        'FG.tail': FGtail,
+        'DE.tail': DEtail,
+        'BC.tail': BCtail,
+        'A.tail': Atail,
+    }
+    # print(f"debug.355: 转弯半径: {d5}")
+
+    # --- fill centers_list ---
+    for k, v in tail_center_dict.items():
+        # --- set ---
+        center_x, center_y = v
+        short, long = get_a_and_b_by_c_and_min_theta(v201, 90 - v4)
+        data_string = f"{k}|{center_x}|{center_y}"
+
+        # --- get center_3_x center_3_y --- 顺时针偏移一定角度
+        center_3_x = center_x + long
+        center_3_y = center_y - short
+        data_string += f"|{center_3_x}|{center_3_y}"
+
+        # --- get center_9_x center_9_y --- 顺时针偏移一定角度
+        center_9_x = center_x - long
+        center_9_y = center_y + short
+        data_string += f"|{center_9_x}|{center_9_y}"
+
+        # --- get center_12_x center_12_y --- 顺时针偏移一定角度
+        center_12_x = center_x + short
+        center_12_y = center_y + long
+        data_string += f"|{center_12_x}|{center_12_y}"
+
+        # --- get center_6_x center_6_y --- 顺时针偏移一定角度
+        center_6_x = center_x - short
+        center_6_y = center_y - long
+        data_string += f"|{center_6_x}|{center_6_y}"
+
+        # --- append ---
+        centers_list.append(data_string)
+
+    # --- fill json_data_dict ---
+    json_data_dict = {
+        "title": "圆心坐标数据",
+        "name|center_x|center_y|center_3_x|center_3_y|center_9_x|center_9_y|center_12_x|center_12_y|center_6_x|center_6_y": centers_list,
+        "total": len(centers_list),
+    }
+
+    # --- save 渣罐坐标数据 ---
+    with open('圆心坐标数据.json', "w", encoding='utf-8') as f:
+        json.dump(json_data_dict, f, indent=4, sort_keys=True, ensure_ascii=False)
+
+
+if __name__ == '__main__':
+    # --- 自测数据 ---
+    L1 = 2, 2
+    L31 = 3, 1
+    N1 = 3, 3
+    N31 = 4, 2
+    # --- 大冶现场数据 ---
+    """
+    18.618844   200.675670   mn-1  "M.1|0|0|18.618844|200.67567",
+    14.24217    8.016018     a-1
+    179.503282   5.019985     a-31
+    183.184822   197.312110   mn-31  "M.31|0|0|183.184822|197.31211",
+    """
+    MN01 = 18.618844, 200.675670
+    A01 = 14.24217, 8.016018
+    A31 = 179.503282, 5.019985
+    MN31 = 183.184822, 197.312110
+    # --- test ---
+    main(MN01, MN31, A01, A31)

+ 5 - 0
project-fastapi-hs/data/SRI2024032814-4point.txt

@@ -0,0 +1,5 @@
+   X            Y         ID
+18.618844   200.675670   mn-1
+14.24217    8.016018     a-1
+179.503282   5.019985     a-31
+183.184822   197.312110   mn-31

+ 13 - 0
project-fastapi-hs/data/倒渣口坐标数据.json

@@ -0,0 +1,13 @@
+{
+    "name|x|y": [
+        "dump.MN|4.18|205.75",
+        "dump.KL|4.18|177.75",
+        "dump.IJ|4.18|149.75",
+        "dump.GH|4.18|121.75",
+        "dump.EF|4.18|93.75",
+        "dump.CD|4.18|65.75",
+        "dump.AB|4.18|37.75"
+    ],
+    "title": "倒渣口坐标数据",
+    "total": 7
+}

+ 20 - 0
project-fastapi-hs/data/圆心坐标数据.json

@@ -0,0 +1,20 @@
+{
+    "name|center_x|center_y|center_3_x|center_3_y|center_9_x|center_9_y|center_12_x|center_12_y|center_6_x|center_6_y": [
+        "LM.head|18.306224428571426|186.9142662857143|32.06762814285714|186.60164671428572|4.544820714285713|187.22688585714286|18.618844|200.67567|17.993604857142852|173.15286257142859",
+        "JK.head|17.680985285714282|159.39145885714285|31.442388999999995|159.07883928571428|3.9195815714285693|159.70407842857142|17.993604857142856|173.15286257142856|17.36836571428571|145.63005514285715",
+        "HI.head|17.055746142857135|131.8686514285714|30.817149857142848|131.55603185714284|3.2943424285714222|132.18127099999998|17.36836571428571|145.63005514285712|16.74312657142856|118.1072477142857",
+        "FG.head|16.43050699999999|104.345844|30.191910714285704|104.03322442857143|2.6691032857142787|104.65846357142857|16.743126571428565|118.1072477142857|16.117887428571418|90.5844402857143",
+        "DE.head|15.805267857142846|76.82303657142857|29.566671571428557|76.510417|2.0438641428571334|77.13565614285714|16.117887428571418|90.5844402857143|15.492648285714274|63.06163285714286",
+        "BC.head|15.1800287142857|49.30022914285715|28.941432428571414|48.98760957142858|1.418624999999988|49.61284871428572|15.492648285714273|63.06163285714286|14.867409142857129|35.538825428571435",
+        "A.head|14.554789571428557|21.777421714285737|28.31619328571427|21.464802142857163|0.7933858571428445|22.09004128571431|14.867409142857129|35.53882542857145|14.242169999999986|8.016018000000024",
+        "LM.tail|182.92185485714285|183.5769582142857|196.65700664285714|183.31399107142855|169.18670307142855|183.83992535714285|183.184822|197.31211|182.6588877142857|169.8418064285714",
+        "JK.tail|182.39592057142858|156.10665464285714|196.13107235714287|155.8436875|168.66076878571428|156.3696217857143|182.65888771428573|169.84180642857143|182.13295342857143|142.37150285714284",
+        "HI.tail|181.86998628571428|128.63635107142855|195.60513807142857|128.3733839285714|168.13483449999998|128.8993182142857|182.13295342857143|142.37150285714284|181.60701914285713|114.90119928571426",
+        "FG.tail|181.344052|101.16604749999998|195.0792037857143|100.90308035714284|167.6089002142857|101.42901464285711|181.60701914285715|114.90119928571426|181.08108485714286|87.4308957142857",
+        "DE.tail|180.8181177142857|73.6957439285714|194.5532695|73.43277678571427|167.0829659285714|73.95871107142854|181.08108485714286|87.4308957142857|180.55515057142856|59.96059214285712",
+        "BC.tail|180.29218342857143|46.225440357142844|194.02733521428573|45.9624732142857|166.55703164285714|46.48840749999999|180.55515057142858|59.96059214285713|180.02921628571428|32.49028857142856",
+        "A.tail|179.76624914285713|18.755136785714257|193.50140092857143|18.492169642857114|166.03109735714284|19.0181039285714|180.02921628571428|32.49028857142854|179.50328199999998|5.01998499999997"
+    ],
+    "title": "圆心坐标数据",
+    "total": 14
+}

+ 440 - 0
project-fastapi-hs/data/渣罐坐标数据.json

@@ -0,0 +1,440 @@
+{
+    "pot_name|pot_x|pot_y|road_center_x|road_center_y": [
+        "A.1|0|0|14.867409142857127|35.538825428571414",
+        "A.2|0|0|20.372802714285697|35.437207533333314",
+        "A.3|0|0|25.87819628571427|35.33558963809522",
+        "A.4|0|0|31.38358985714284|35.23397174285712",
+        "A.5|0|0|36.888983428571414|35.13235384761903",
+        "A.6|0|0|42.39437699999999|35.03073595238093",
+        "A.7|0|0|47.899770571428554|34.92911805714283",
+        "A.8|0|0|53.40516414285713|34.82750016190474",
+        "A.9|0|0|58.9105577142857|34.72588226666664",
+        "A.10|0|0|64.41595128571427|34.624264371428545",
+        "A.11|0|0|69.92134485714286|34.522646476190445",
+        "A.12|0|0|75.42673842857141|34.421028580952346",
+        "A.13|0|0|80.93213199999998|34.31941068571425",
+        "A.14|0|0|86.43752557142857|34.21779279047615",
+        "A.15|0|0|91.94291914285714|34.11617489523806",
+        "A.16|0|0|97.4483127142857|34.01455699999996",
+        "A.17|0|0|102.95370628571428|33.91293910476186",
+        "A.18|0|0|108.45909985714285|33.81132120952377",
+        "A.19|0|0|113.96449342857143|33.70970331428567",
+        "A.20|0|0|119.469887|33.608085419047576",
+        "A.21|0|0|124.97528057142857|33.50646752380948",
+        "A.22|0|0|130.48067414285714|33.404849628571384",
+        "A.23|0|0|135.9860677142857|33.303231733333284",
+        "A.24|0|0|141.4914612857143|33.201613838095184",
+        "A.25|0|0|146.99685485714284|33.09999594285709",
+        "A.26|0|0|152.50224842857142|32.99837804761899",
+        "A.27|0|0|158.007642|32.8967601523809",
+        "A.28|0|0|163.5130355714286|32.7951422571428",
+        "A.29|0|0|169.01842914285714|32.6935243619047",
+        "A.30|0|0|174.52382271428573|32.59190646666661",
+        "A.31|0|0|180.02921628571428|32.49028857142851",
+        "B.1|0|0|14.867409142857127|35.538825428571414",
+        "B.2|0|0|20.372802714285697|35.437207533333314",
+        "B.3|0|0|25.87819628571427|35.33558963809522",
+        "B.4|0|0|31.38358985714284|35.23397174285712",
+        "B.5|0|0|36.888983428571414|35.13235384761903",
+        "B.6|0|0|42.39437699999999|35.03073595238093",
+        "B.7|0|0|47.899770571428554|34.92911805714283",
+        "B.8|0|0|53.40516414285713|34.82750016190474",
+        "B.9|0|0|58.9105577142857|34.72588226666664",
+        "B.10|0|0|64.41595128571427|34.624264371428545",
+        "B.11|0|0|69.92134485714286|34.522646476190445",
+        "B.12|0|0|75.42673842857141|34.421028580952346",
+        "B.13|0|0|80.93213199999998|34.31941068571425",
+        "B.14|0|0|86.43752557142857|34.21779279047615",
+        "B.15|0|0|91.94291914285714|34.11617489523806",
+        "B.16|0|0|97.4483127142857|34.01455699999996",
+        "B.17|0|0|102.95370628571428|33.91293910476186",
+        "B.18|0|0|108.45909985714285|33.81132120952377",
+        "B.19|0|0|113.96449342857143|33.70970331428567",
+        "B.20|0|0|119.469887|33.608085419047576",
+        "B.21|0|0|124.97528057142857|33.50646752380948",
+        "B.22|0|0|130.48067414285714|33.404849628571384",
+        "B.23|0|0|135.9860677142857|33.303231733333284",
+        "B.24|0|0|141.4914612857143|33.201613838095184",
+        "B.25|0|0|146.99685485714284|33.09999594285709",
+        "B.26|0|0|152.50224842857142|32.99837804761899",
+        "B.27|0|0|158.007642|32.8967601523809",
+        "B.28|0|0|163.5130355714286|32.7951422571428",
+        "B.29|0|0|169.01842914285714|32.6935243619047",
+        "B.30|0|0|174.52382271428573|32.59190646666661",
+        "B.31|0|0|180.02921628571428|32.49028857142851",
+        "C.1|0|0|15.492648285714273|63.061632857142854",
+        "C.2|0|0|20.994731695238084|62.95826483333333",
+        "C.3|0|0|26.496815104761893|62.8548968095238",
+        "C.4|0|0|31.998898514285706|62.75152878571428",
+        "C.5|0|0|37.50098192380951|62.64816076190475",
+        "C.6|0|0|43.003065333333325|62.544792738095225",
+        "C.7|0|0|48.50514874285714|62.441424714285695",
+        "C.8|0|0|54.00723215238095|62.33805669047617",
+        "C.9|0|0|59.50931556190476|62.23468866666664",
+        "C.10|0|0|65.01139897142856|62.13132064285712",
+        "C.11|0|0|70.51348238095237|62.02795261904759",
+        "C.12|0|0|76.01556579047619|61.924584595238066",
+        "C.13|0|0|81.5176492|61.821216571428536",
+        "C.14|0|0|87.0197326095238|61.71784854761901",
+        "C.15|0|0|92.52181601904762|61.61448052380948",
+        "C.16|0|0|98.02389942857143|61.51111249999996",
+        "C.17|0|0|103.52598283809523|61.40774447619043",
+        "C.18|0|0|109.02806624761904|61.30437645238091",
+        "C.19|0|0|114.53014965714284|61.20100842857138",
+        "C.20|0|0|120.03223306666665|61.097640404761854",
+        "C.21|0|0|125.53431647619047|60.994272380952324",
+        "C.22|0|0|131.0363998857143|60.8909043571428",
+        "C.23|0|0|136.5384832952381|60.78753633333327",
+        "C.24|0|0|142.04056670476191|60.68416830952375",
+        "C.25|0|0|147.54265011428572|60.58080028571422",
+        "C.26|0|0|153.04473352380953|60.477432261904696",
+        "C.27|0|0|158.54681693333333|60.374064238095166",
+        "C.28|0|0|164.04890034285714|60.27069621428564",
+        "C.29|0|0|169.55098375238097|60.16732819047611",
+        "C.30|0|0|175.05306716190475|60.06396016666659",
+        "C.31|0|0|180.55515057142858|59.96059214285706",
+        "D.1|0|0|15.492648285714273|63.061632857142854",
+        "D.2|0|0|20.994731695238084|62.95826483333333",
+        "D.3|0|0|26.496815104761893|62.8548968095238",
+        "D.4|0|0|31.998898514285706|62.75152878571428",
+        "D.5|0|0|37.50098192380951|62.64816076190475",
+        "D.6|0|0|43.003065333333325|62.544792738095225",
+        "D.7|0|0|48.50514874285714|62.441424714285695",
+        "D.8|0|0|54.00723215238095|62.33805669047617",
+        "D.9|0|0|59.50931556190476|62.23468866666664",
+        "D.10|0|0|65.01139897142856|62.13132064285712",
+        "D.11|0|0|70.51348238095237|62.02795261904759",
+        "D.12|0|0|76.01556579047619|61.924584595238066",
+        "D.13|0|0|81.5176492|61.821216571428536",
+        "D.14|0|0|87.0197326095238|61.71784854761901",
+        "D.15|0|0|92.52181601904762|61.61448052380948",
+        "D.16|0|0|98.02389942857143|61.51111249999996",
+        "D.17|0|0|103.52598283809523|61.40774447619043",
+        "D.18|0|0|109.02806624761904|61.30437645238091",
+        "D.19|0|0|114.53014965714284|61.20100842857138",
+        "D.20|0|0|120.03223306666665|61.097640404761854",
+        "D.21|0|0|125.53431647619047|60.994272380952324",
+        "D.22|0|0|131.0363998857143|60.8909043571428",
+        "D.23|0|0|136.5384832952381|60.78753633333327",
+        "D.24|0|0|142.04056670476191|60.68416830952375",
+        "D.25|0|0|147.54265011428572|60.58080028571422",
+        "D.26|0|0|153.04473352380953|60.477432261904696",
+        "D.27|0|0|158.54681693333333|60.374064238095166",
+        "D.28|0|0|164.04890034285714|60.27069621428564",
+        "D.29|0|0|169.55098375238097|60.16732819047611",
+        "D.30|0|0|175.05306716190475|60.06396016666659",
+        "D.31|0|0|180.55515057142858|59.96059214285706",
+        "E.1|0|0|16.117887428571418|90.5844402857143",
+        "E.2|0|0|21.616660676190467|90.47932213333334",
+        "E.3|0|0|27.115433923809515|90.37420398095239",
+        "E.4|0|0|32.61420717142856|90.26908582857143",
+        "E.5|0|0|38.11298041904762|90.16396767619048",
+        "E.6|0|0|43.61175366666666|90.05884952380953",
+        "E.7|0|0|49.110526914285714|89.95373137142857",
+        "E.8|0|0|54.609300161904756|89.84861321904762",
+        "E.9|0|0|60.10807340952381|89.74349506666667",
+        "E.10|0|0|65.60684665714285|89.63837691428571",
+        "E.11|0|0|71.10561990476191|89.53325876190476",
+        "E.12|0|0|76.60439315238095|89.42814060952381",
+        "E.13|0|0|82.1031664|89.32302245714286",
+        "E.14|0|0|87.60193964761906|89.2179043047619",
+        "E.15|0|0|93.1007128952381|89.11278615238095",
+        "E.16|0|0|98.59948614285715|89.007668",
+        "E.17|0|0|104.0982593904762|88.90254984761904",
+        "E.18|0|0|109.59703263809524|88.79743169523809",
+        "E.19|0|0|115.0958058857143|88.69231354285714",
+        "E.20|0|0|120.59457913333334|88.58719539047618",
+        "E.21|0|0|126.0933523809524|88.48207723809523",
+        "E.22|0|0|131.59212562857144|88.37695908571428",
+        "E.23|0|0|137.09089887619047|88.27184093333332",
+        "E.24|0|0|142.58967212380952|88.16672278095237",
+        "E.25|0|0|148.08844537142858|88.06160462857142",
+        "E.26|0|0|153.58721861904763|87.95648647619046",
+        "E.27|0|0|159.0859918666667|87.85136832380951",
+        "E.28|0|0|164.58476511428572|87.74625017142856",
+        "E.29|0|0|170.08353836190477|87.6411320190476",
+        "E.30|0|0|175.58231160952383|87.53601386666665",
+        "E.31|0|0|181.08108485714286|87.4308957142857",
+        "F.1|0|0|16.117887428571418|90.5844402857143",
+        "F.2|0|0|21.616660676190467|90.47932213333334",
+        "F.3|0|0|27.115433923809515|90.37420398095239",
+        "F.4|0|0|32.61420717142856|90.26908582857143",
+        "F.5|0|0|38.11298041904762|90.16396767619048",
+        "F.6|0|0|43.61175366666666|90.05884952380953",
+        "F.7|0|0|49.110526914285714|89.95373137142857",
+        "F.8|0|0|54.609300161904756|89.84861321904762",
+        "F.9|0|0|60.10807340952381|89.74349506666667",
+        "F.10|0|0|65.60684665714285|89.63837691428571",
+        "F.11|0|0|71.10561990476191|89.53325876190476",
+        "F.12|0|0|76.60439315238095|89.42814060952381",
+        "F.13|0|0|82.1031664|89.32302245714286",
+        "F.14|0|0|87.60193964761906|89.2179043047619",
+        "F.15|0|0|93.1007128952381|89.11278615238095",
+        "F.16|0|0|98.59948614285715|89.007668",
+        "F.17|0|0|104.0982593904762|88.90254984761904",
+        "F.18|0|0|109.59703263809524|88.79743169523809",
+        "F.19|0|0|115.0958058857143|88.69231354285714",
+        "F.20|0|0|120.59457913333334|88.58719539047618",
+        "F.21|0|0|126.0933523809524|88.48207723809523",
+        "F.22|0|0|131.59212562857144|88.37695908571428",
+        "F.23|0|0|137.09089887619047|88.27184093333332",
+        "F.24|0|0|142.58967212380952|88.16672278095237",
+        "F.25|0|0|148.08844537142858|88.06160462857142",
+        "F.26|0|0|153.58721861904763|87.95648647619046",
+        "F.27|0|0|159.0859918666667|87.85136832380951",
+        "F.28|0|0|164.58476511428572|87.74625017142856",
+        "F.29|0|0|170.08353836190477|87.6411320190476",
+        "F.30|0|0|175.58231160952383|87.53601386666665",
+        "F.31|0|0|181.08108485714286|87.4308957142857",
+        "G.1|0|0|16.743126571428565|118.1072477142857",
+        "G.2|0|0|22.23858965714285|118.00037943333331",
+        "G.3|0|0|27.734052742857134|117.89351115238091",
+        "G.4|0|0|33.22951582857142|117.78664287142851",
+        "G.5|0|0|38.7249789142857|117.6797745904761",
+        "G.6|0|0|44.22044199999999|117.5729063095237",
+        "G.7|0|0|49.71590508571427|117.4660380285713",
+        "G.8|0|0|55.21136817142856|117.3591697476189",
+        "G.9|0|0|60.70683125714284|117.25230146666651",
+        "G.10|0|0|66.20229434285713|117.14543318571411",
+        "G.11|0|0|71.6977574285714|117.0385649047617",
+        "G.12|0|0|77.1932205142857|116.9316966238093",
+        "G.13|0|0|82.68868359999998|116.8248283428569",
+        "G.14|0|0|88.18414668571427|116.7179600619045",
+        "G.15|0|0|93.67960977142855|116.61109178095211",
+        "G.16|0|0|99.17507285714284|116.50422349999971",
+        "G.17|0|0|104.67053594285711|116.3973552190473",
+        "G.18|0|0|110.16599902857139|116.2904869380949",
+        "G.19|0|0|115.66146211428568|116.1836186571425",
+        "G.20|0|0|121.15692519999996|116.0767503761901",
+        "G.21|0|0|126.65238828571425|115.96988209523771",
+        "G.22|0|0|132.14785137142854|115.86301381428531",
+        "G.23|0|0|137.64331445714282|115.7561455333329",
+        "G.24|0|0|143.1387775428571|115.6492772523805",
+        "G.25|0|0|148.6342406285714|115.5424089714281",
+        "G.26|0|0|154.12970371428568|115.4355406904757",
+        "G.27|0|0|159.6251668|115.3286724095233",
+        "G.28|0|0|165.12062988571424|115.22180412857091",
+        "G.29|0|0|170.61609297142854|115.1149358476185",
+        "G.30|0|0|176.11155605714282|115.0080675666661",
+        "G.31|0|0|181.60701914285713|114.9011992857137",
+        "H.1|0|0|16.743126571428565|118.1072477142857",
+        "H.2|0|0|22.23858965714285|118.00037943333331",
+        "H.3|0|0|27.734052742857134|117.89351115238091",
+        "H.4|0|0|33.22951582857142|117.78664287142851",
+        "H.5|0|0|38.7249789142857|117.6797745904761",
+        "H.6|0|0|44.22044199999999|117.5729063095237",
+        "H.7|0|0|49.71590508571427|117.4660380285713",
+        "H.8|0|0|55.21136817142856|117.3591697476189",
+        "H.9|0|0|60.70683125714284|117.25230146666651",
+        "H.10|0|0|66.20229434285713|117.14543318571411",
+        "H.11|0|0|71.6977574285714|117.0385649047617",
+        "H.12|0|0|77.1932205142857|116.9316966238093",
+        "H.13|0|0|82.68868359999998|116.8248283428569",
+        "H.14|0|0|88.18414668571427|116.7179600619045",
+        "H.15|0|0|93.67960977142855|116.61109178095211",
+        "H.16|0|0|99.17507285714284|116.50422349999971",
+        "H.17|0|0|104.67053594285711|116.3973552190473",
+        "H.18|0|0|110.16599902857139|116.2904869380949",
+        "H.19|0|0|115.66146211428568|116.1836186571425",
+        "H.20|0|0|121.15692519999996|116.0767503761901",
+        "H.21|0|0|126.65238828571425|115.96988209523771",
+        "H.22|0|0|132.14785137142854|115.86301381428531",
+        "H.23|0|0|137.64331445714282|115.7561455333329",
+        "H.24|0|0|143.1387775428571|115.6492772523805",
+        "H.25|0|0|148.6342406285714|115.5424089714281",
+        "H.26|0|0|154.12970371428568|115.4355406904757",
+        "H.27|0|0|159.6251668|115.3286724095233",
+        "H.28|0|0|165.12062988571424|115.22180412857091",
+        "H.29|0|0|170.61609297142854|115.1149358476185",
+        "H.30|0|0|176.11155605714282|115.0080675666661",
+        "H.31|0|0|181.60701914285713|114.9011992857137",
+        "I.1|0|0|17.36836571428571|145.63005514285715",
+        "I.2|0|0|22.860518638095233|145.52143673333333",
+        "I.3|0|0|28.352671561904756|145.41281832380955",
+        "I.4|0|0|33.844824485714284|145.30419991428573",
+        "I.5|0|0|39.336977409523804|145.19558150476195",
+        "I.6|0|0|44.829130333333325|145.08696309523813",
+        "I.7|0|0|50.32128325714285|144.97834468571435",
+        "I.8|0|0|55.81343618095237|144.86972627619053",
+        "I.9|0|0|61.3055891047619|144.76110786666675",
+        "I.10|0|0|66.79774202857143|144.65248945714293",
+        "I.11|0|0|72.28989495238095|144.54387104761915",
+        "I.12|0|0|77.78204787619048|144.43525263809533",
+        "I.13|0|0|83.27420079999999|144.32663422857155",
+        "I.14|0|0|88.76635372380952|144.21801581904774",
+        "I.15|0|0|94.25850664761904|144.10939740952395",
+        "I.16|0|0|99.75065957142857|144.00077900000014",
+        "I.17|0|0|105.2428124952381|143.89216059047635",
+        "I.18|0|0|110.73496541904763|143.78354218095254",
+        "I.19|0|0|116.22711834285715|143.67492377142875",
+        "I.20|0|0|121.71927126666665|143.56630536190494",
+        "I.21|0|0|127.21142419047618|143.45768695238115",
+        "I.22|0|0|132.7035771142857|143.34906854285734",
+        "I.23|0|0|138.19573003809523|143.24045013333355",
+        "I.24|0|0|143.68788296190476|143.13183172380974",
+        "I.25|0|0|149.1800358857143|143.02321331428593",
+        "I.26|0|0|154.67218880952382|142.91459490476214",
+        "I.27|0|0|160.16434173333334|142.80597649523833",
+        "I.28|0|0|165.65649465714287|142.69735808571454",
+        "I.29|0|0|171.14864758095237|142.58873967619073",
+        "I.30|0|0|176.64080050476193|142.48012126666694",
+        "I.31|0|0|182.13295342857143|142.37150285714313",
+        "J.1|0|0|17.36836571428571|145.63005514285715",
+        "J.2|0|0|22.860518638095233|145.52143673333333",
+        "J.3|0|0|28.352671561904756|145.41281832380955",
+        "J.4|0|0|33.844824485714284|145.30419991428573",
+        "J.5|0|0|39.336977409523804|145.19558150476195",
+        "J.6|0|0|44.829130333333325|145.08696309523813",
+        "J.7|0|0|50.32128325714285|144.97834468571435",
+        "J.8|0|0|55.81343618095237|144.86972627619053",
+        "J.9|0|0|61.3055891047619|144.76110786666675",
+        "J.10|0|0|66.79774202857143|144.65248945714293",
+        "J.11|0|0|72.28989495238095|144.54387104761915",
+        "J.12|0|0|77.78204787619048|144.43525263809533",
+        "J.13|0|0|83.27420079999999|144.32663422857155",
+        "J.14|0|0|88.76635372380952|144.21801581904774",
+        "J.15|0|0|94.25850664761904|144.10939740952395",
+        "J.16|0|0|99.75065957142857|144.00077900000014",
+        "J.17|0|0|105.2428124952381|143.89216059047635",
+        "J.18|0|0|110.73496541904763|143.78354218095254",
+        "J.19|0|0|116.22711834285715|143.67492377142875",
+        "J.20|0|0|121.71927126666665|143.56630536190494",
+        "J.21|0|0|127.21142419047618|143.45768695238115",
+        "J.22|0|0|132.7035771142857|143.34906854285734",
+        "J.23|0|0|138.19573003809523|143.24045013333355",
+        "J.24|0|0|143.68788296190476|143.13183172380974",
+        "J.25|0|0|149.1800358857143|143.02321331428593",
+        "J.26|0|0|154.67218880952382|142.91459490476214",
+        "J.27|0|0|160.16434173333334|142.80597649523833",
+        "J.28|0|0|165.65649465714287|142.69735808571454",
+        "J.29|0|0|171.14864758095237|142.58873967619073",
+        "J.30|0|0|176.64080050476193|142.48012126666694",
+        "J.31|0|0|182.13295342857143|142.37150285714313",
+        "K.1|0|0|17.993604857142856|173.15286257142856",
+        "K.2|0|0|23.48244761904762|173.0424940333333",
+        "K.3|0|0|28.97129038095238|172.932125495238",
+        "K.4|0|0|34.46013314285714|172.82175695714275",
+        "K.5|0|0|39.9489759047619|172.7113884190475",
+        "K.6|0|0|45.43781866666666|172.60101988095224",
+        "K.7|0|0|50.92666142857142|172.49065134285695",
+        "K.8|0|0|56.415504190476184|172.3802828047617",
+        "K.9|0|0|61.90434695238095|172.26991426666643",
+        "K.10|0|0|67.39318971428571|172.15954572857115",
+        "K.11|0|0|72.88203247619046|172.0491771904759",
+        "K.12|0|0|78.37087523809522|171.93880865238063",
+        "K.13|0|0|83.85971799999999|171.82844011428537",
+        "K.14|0|0|89.34856076190475|171.71807157619008",
+        "K.15|0|0|94.83740352380951|171.60770303809483",
+        "K.16|0|0|100.32624628571428|171.49733449999957",
+        "K.17|0|0|105.81508904761904|171.38696596190428",
+        "K.18|0|0|111.30393180952379|171.27659742380902",
+        "K.19|0|0|116.79277457142855|171.16622888571376",
+        "K.20|0|0|122.28161733333332|171.0558603476185",
+        "K.21|0|0|127.77046009523808|170.94549180952322",
+        "K.22|0|0|133.25930285714284|170.83512327142796",
+        "K.23|0|0|138.7481456190476|170.7247547333327",
+        "K.24|0|0|144.23698838095237|170.61438619523742",
+        "K.25|0|0|149.72583114285712|170.50401765714216",
+        "K.26|0|0|155.21467390476187|170.3936491190469",
+        "K.27|0|0|160.70351666666664|170.28328058095164",
+        "K.28|0|0|166.1923594285714|170.17291204285635",
+        "K.29|0|0|171.68120219047617|170.0625435047611",
+        "K.30|0|0|177.17004495238092|169.95217496666584",
+        "K.31|0|0|182.6588877142857|169.84180642857055",
+        "L.1|0|0|17.993604857142856|173.15286257142856",
+        "L.2|0|0|23.48244761904762|173.0424940333333",
+        "L.3|0|0|28.97129038095238|172.932125495238",
+        "L.4|0|0|34.46013314285714|172.82175695714275",
+        "L.5|0|0|39.9489759047619|172.7113884190475",
+        "L.6|0|0|45.43781866666666|172.60101988095224",
+        "L.7|0|0|50.92666142857142|172.49065134285695",
+        "L.8|0|0|56.415504190476184|172.3802828047617",
+        "L.9|0|0|61.90434695238095|172.26991426666643",
+        "L.10|0|0|67.39318971428571|172.15954572857115",
+        "L.11|0|0|72.88203247619046|172.0491771904759",
+        "L.12|0|0|78.37087523809522|171.93880865238063",
+        "L.13|0|0|83.85971799999999|171.82844011428537",
+        "L.14|0|0|89.34856076190475|171.71807157619008",
+        "L.15|0|0|94.83740352380951|171.60770303809483",
+        "L.16|0|0|100.32624628571428|171.49733449999957",
+        "L.17|0|0|105.81508904761904|171.38696596190428",
+        "L.18|0|0|111.30393180952379|171.27659742380902",
+        "L.19|0|0|116.79277457142855|171.16622888571376",
+        "L.20|0|0|122.28161733333332|171.0558603476185",
+        "L.21|0|0|127.77046009523808|170.94549180952322",
+        "L.22|0|0|133.25930285714284|170.83512327142796",
+        "L.23|0|0|138.7481456190476|170.7247547333327",
+        "L.24|0|0|144.23698838095237|170.61438619523742",
+        "L.25|0|0|149.72583114285712|170.50401765714216",
+        "L.26|0|0|155.21467390476187|170.3936491190469",
+        "L.27|0|0|160.70351666666664|170.28328058095164",
+        "L.28|0|0|166.1923594285714|170.17291204285635",
+        "L.29|0|0|171.68120219047617|170.0625435047611",
+        "L.30|0|0|177.17004495238092|169.95217496666584",
+        "L.31|0|0|182.6588877142857|169.84180642857055",
+        "M.1|0|0|18.618844|200.67567",
+        "M.2|0|0|24.1043766|200.56355133333332",
+        "M.3|0|0|29.5899092|200.45143266666668",
+        "M.4|0|0|35.0754418|200.339314",
+        "M.5|0|0|40.5609744|200.22719533333333",
+        "M.6|0|0|46.046507000000005|200.11507666666665",
+        "M.7|0|0|51.532039600000004|200.002958",
+        "M.8|0|0|57.017572200000004|199.89083933333333",
+        "M.9|0|0|62.5031048|199.77872066666666",
+        "M.10|0|0|67.9886374|199.66660199999998",
+        "M.11|0|0|73.47417|199.55448333333334",
+        "M.12|0|0|78.9597026|199.44236466666666",
+        "M.13|0|0|84.4452352|199.330246",
+        "M.14|0|0|89.9307678|199.21812733333334",
+        "M.15|0|0|95.4163004|199.10600866666667",
+        "M.16|0|0|100.901833|198.99389",
+        "M.17|0|0|106.3873656|198.88177133333332",
+        "M.18|0|0|111.87289820000001|198.76965266666667",
+        "M.19|0|0|117.35843080000001|198.657534",
+        "M.20|0|0|122.8439634|198.54541533333332",
+        "M.21|0|0|128.329496|198.43329666666665",
+        "M.22|0|0|133.8150286|198.321178",
+        "M.23|0|0|139.3005612|198.20905933333333",
+        "M.24|0|0|144.7860938|198.09694066666665",
+        "M.25|0|0|150.2716264|197.984822",
+        "M.26|0|0|155.75715900000003|197.87270333333333",
+        "M.27|0|0|161.2426916|197.76058466666666",
+        "M.28|0|0|166.7282242|197.64846599999998",
+        "M.29|0|0|172.2137568|197.53634733333334",
+        "M.30|0|0|177.69928940000003|197.42422866666666",
+        "M.31|0|0|183.184822|197.31211",
+        "N.1|0|0|18.618844|200.67567",
+        "N.2|0|0|24.1043766|200.56355133333332",
+        "N.3|0|0|29.5899092|200.45143266666668",
+        "N.4|0|0|35.0754418|200.339314",
+        "N.5|0|0|40.5609744|200.22719533333333",
+        "N.6|0|0|46.046507000000005|200.11507666666665",
+        "N.7|0|0|51.532039600000004|200.002958",
+        "N.8|0|0|57.017572200000004|199.89083933333333",
+        "N.9|0|0|62.5031048|199.77872066666666",
+        "N.10|0|0|67.9886374|199.66660199999998",
+        "N.11|0|0|73.47417|199.55448333333334",
+        "N.12|0|0|78.9597026|199.44236466666666",
+        "N.13|0|0|84.4452352|199.330246",
+        "N.14|0|0|89.9307678|199.21812733333334",
+        "N.15|0|0|95.4163004|199.10600866666667",
+        "N.16|0|0|100.901833|198.99389",
+        "N.17|0|0|106.3873656|198.88177133333332",
+        "N.18|0|0|111.87289820000001|198.76965266666667",
+        "N.19|0|0|117.35843080000001|198.657534",
+        "N.20|0|0|122.8439634|198.54541533333332",
+        "N.21|0|0|128.329496|198.43329666666665",
+        "N.22|0|0|133.8150286|198.321178",
+        "N.23|0|0|139.3005612|198.20905933333333",
+        "N.24|0|0|144.7860938|198.09694066666665",
+        "N.25|0|0|150.2716264|197.984822",
+        "N.26|0|0|155.75715900000003|197.87270333333333",
+        "N.27|0|0|161.2426916|197.76058466666666",
+        "N.28|0|0|166.7282242|197.64846599999998",
+        "N.29|0|0|172.2137568|197.53634733333334",
+        "N.30|0|0|177.69928940000003|197.42422866666666",
+        "N.31|0|0|183.184822|197.31211"
+    ],
+    "title": "渣罐坐标数据",
+    "total": 434
+}

+ 54 - 0
project-fastapi-hs/default_data.py

@@ -0,0 +1,54 @@
+data = {
+    'enable': True,
+    'config': {
+        # --- 用户配置 ---
+        'Role&User': [
+            {
+                'role_info': {
+                    'role_type': 1,
+                    'role_name': '超级管理员',
+                    'switch_list': [
+                        'admin'
+                    ],
+                },
+                'users_info': [
+                    {
+                        'username': 'admin',
+                        'password': '123456',
+                    }
+                ],
+            },
+        ],
+        # --- 车辆配置 ---
+        'VehicleInfo': [
+            {'name': '3号车', 'address': '192.168.131.180'},
+            {'name': '5号车', 'address': '192.168.131.xxx'},
+        ],
+        # --- 倒渣口坐标数据 ---
+        # 'aaa': [
+        #     {'name': '3号车', 'address': '192.168.131.180'},
+        #     {'name': '5号车', 'address': '192.168.131.xxx'},
+        # ],
+        # --- 渣包坐标数据 ---
+        # 'bbb': [
+        #     {'name': '3号车', 'address': '192.168.131.180'},
+        #     {'name': '5号车', 'address': '192.168.131.xxx'},
+        # ],
+        # --- 转弯圆心点坐标数据 ---
+        # 'ccc': [
+        #     {'name': '3号车', 'address': '192.168.131.180'},
+        #     {'name': '5号车', 'address': '192.168.131.xxx'},
+        # ],
+        # --- 全局配置 ---
+        'GlobalVariable': {
+            'CameraConfig': {
+
+                # 海康 人脸摄像机 中建八局 第4现场 奥东项目
+                # 'camera_rtsp': 'rtsp://admin:DEVdev123@192.168.1.64:554/h264/ch1/main/av_stream',
+                # 'camera_ipv4': '192.168.1.64',
+                # 'camera_user': 'admin',
+                # 'camera_pass': 'DEVdev123',
+            }
+        }
+    }
+}

+ 84 - 0
project-fastapi-hs/default_data_insert.py

@@ -0,0 +1,84 @@
+from werkzeug.security import generate_password_hash
+from hub import methods, Global
+
+
+def default_data_insert():
+    # --- check ---
+    from default_data import data
+    if not data.get('enable'):
+        return
+    config = data.get('config')
+
+    # --- check Role&User ---
+    d1 = {
+        'role': [],
+        'user': [],
+    }
+    for item in config.get('Role&User'):
+
+        # --- insert role_info ---
+        role_info = item.get('role_info')
+        unique_dict = {
+            'role_type': role_info.get('role_type'),
+        }
+        mdb_role = Global.mdb.get_one('UserRole', unique_dict)
+
+        # --- check ---
+        if not mdb_role:
+            update_dict = {
+                'role_type': role_info.get('role_type'),
+                'role_name': role_info.get('role_name'),
+                'switch_list': role_info.get('switch_list'),
+                'create_at': methods.now_ts(),
+            }
+            rid = Global.mdb.update_one('UserRole', unique_dict, update_dict)
+            d1['role'].append(rid)
+        else:
+            rid = str(mdb_role.get('_id'))
+            d1['role'].append(rid)
+
+        # --- insert users_info ---
+        users_info = item.get('users_info')
+        for user in users_info:
+            unique_dict = {'username': user.get('username')}
+            # mdb_user = Global.mdb.get_one('User', unique_dict)
+            mdb_user = Global.mdb.get_one('UserInfo', unique_dict)
+
+            # --- check ---
+            if not mdb_user:
+                update_dict = {
+                    'password': generate_password_hash(user.get('password')),
+                    'role_type': role_info.get('role_type'),
+                    'create_at': methods.now_ts(),
+                }
+                # uid = Global.mdb.update_one('User', unique_dict, update_dict)
+                uid = Global.mdb.update_one('UserInfo', unique_dict, update_dict)
+                d1['user'].append(uid)
+            else:
+                uid = str(mdb_user.get('_id'))
+                d1['user'].append(uid)
+    methods.debug_log('default_data_insert.default_data_insert.60', f"#d1: {d1}")
+
+    # --- check VehicleInfo ---
+    for item in config.get('VehicleInfo'):
+        unique_dict = {'address': item.get('address')}
+        data = Global.mdb.get_one('VehicleInfo', unique_dict)
+        if not data:
+            update_dict = {
+                'name': item.get('name'),
+                'address': item.get('address'),
+                'state': 1,
+            }
+            uid = Global.mdb.update_one('VehicleInfo', unique_dict, update_dict)
+
+    # --- check ---
+    # unique_dict = {'name': 'CameraConfig'}
+    # item = Global.mdb.get_one('GlobalVariable', unique_dict)
+    # data = item.get('args', {})
+    # data['camera_rtsp'] = config.get('GlobalVariable', {}).get('CameraConfig', {}).get('camera_rtsp', '')
+    # data['camera_ipv4'] = config.get('GlobalVariable', {}).get('CameraConfig', {}).get('camera_ipv4', '')
+    # data['camera_user'] = config.get('GlobalVariable', {}).get('CameraConfig', {}).get('camera_user', '')
+    # data['camera_pass'] = config.get('GlobalVariable', {}).get('CameraConfig', {}).get('camera_pass', '')
+    # update_dict = {'args': data}
+    # Global.mdb.update_one('GlobalVariable', unique_dict, update_dict)
+    # methods.debug_log('default_data_insert.default_data_insert', f"m-68: {data}")

+ 38 - 0
project-fastapi-hs/hub.py

@@ -0,0 +1,38 @@
+import sys
+import importlib
+
+sys.path.append('../module-py')
+methods = importlib.import_module(f"xlib")
+
+
+# camera_handle = importlib.import_module(f"libraries.base_external.camera_by_cv2")
+# numpy_method = importlib.import_module(f"libraries.base_external.data_by_numpy")
+
+
+class Global(object):
+    # --- 时序数据库 ---
+    # xdb = importlib.import_module(f"clients.db_influx").Client(host='fra-middleware-influx', port=8086,
+    #                                                            database='vms')
+
+    # --- 业务数据数据库 ---
+    # mdb = importlib.import_module(f"clients.db_mongo").Client(host='sri-thirdparty-mongo', port=27017, database='ar',
+    #                                                           username='admin', password='admin')
+    # mdb = importlib.import_module(f"xclient.xmongo").Client(host='58.34.94.176', port=7030, database='hs',
+    #                                                         username='admin', password='admin')
+    mdb = importlib.import_module(f"xclient.xmongo").Client(host='192.168.131.23', port=7030, database='hs',
+                                                            username='admin', password='admin')
+    # mysql = importlib.import_module(f"clients.db_maria2").Client(host='127.0.0.1', port=3306, database='ar',
+    #                                                              username='root', password='20221212!')
+    # mysql = importlib.import_module(f"xclient.db_maria2").Client(host='58.34.94.176', port=8806, database='hs',
+    #                                                              username='root', password='rootroot&123123')
+
+    # --- 缓存数据库 ---
+    # rdb = importlib.import_module(f"clients.db_redis").Client(db=0, host='fra-middleware-redis', port=6379)
+
+    # SSHClient = importlib.import_module(f"clients.l4_ssh_by_paramiko").Client
+
+    # local_api = importlib.import_module(f"apis.local.api").Api()
+
+    mccbts_agent = importlib.import_module(f"xapi.mccbts.api").API()
+    enfei_api = importlib.import_module(f"xapi.enfei.api").API()
+    http_api = importlib.import_module(f"xapi.ros1.api_for_hs").API(address='192.168.191.91', port=8000)

+ 10 - 0
project-fastapi-hs/main.py

@@ -0,0 +1,10 @@
+import uvicorn
+
+if __name__ == "__main__":
+    # --- check default data ---
+    from default_data_insert import default_data_insert
+
+    default_data_insert()
+
+    # uvicorn.run('app:app', host='0.0.0.0', port=9000)
+    uvicorn.run('app:app', host='0.0.0.0', port=9000, reload=True, debug=True)

+ 82 - 0
project-fastapi-hs/main.spec

@@ -0,0 +1,82 @@
+# 用于pyinstaller打包用的配置文件
+# -*- mode: python ; coding: utf-8 -*-
+block_cipher = None
+a = Analysis(
+    [
+        'main.py',
+    ],
+    pathex = [
+
+        '../supplement-python',
+
+    ],
+    binaries = [
+
+        # find / -name "*_dl.cpython-36m-aarch64-linux-gnu.so*"
+        ('/usr/lib/aarch64-linux-gnu/libxcb.so.1', '.'),
+        ('/usr/lib/aarch64-linux-gnu/libdrm.so.2', '.'),
+
+    ],
+    datas = [
+
+    ],
+    hiddenimports = [
+
+        # --- for component ---
+        'actions',
+        'api',
+
+        # --- for supplement ---
+        'libraries.base_original',
+        'libraries.base_external.loop_by_aps',
+        'libraries.base_external.camera_by_cv2',
+        'libraries.base_external.data_by_numpy',
+        'clients.l4_ssh_by_paramiko',
+        'clients.db_mongo',
+        'clients.db_redis',
+        'clients.db_influx',
+        'apis.local.api',
+        'decorators',
+
+        # --- for middleware ---
+        'uvicorn.logging',
+        'uvicorn.loops',
+        'uvicorn.loops.auto',
+        'uvicorn.protocols',
+        'uvicorn.protocols.http',
+        'uvicorn.protocols.http.auto',
+        'uvicorn.protocols.websockets',
+        'uvicorn.protocols.websockets.auto',
+        'uvicorn.lifespan',
+        'uvicorn.lifespan.on',
+        'redis',
+        'pymongo',
+        'paramiko',
+        'apscheduler',
+
+    ],
+    hookspath = [],
+    runtime_hooks = [],
+    excludes = [],
+    win_no_prefer_redirects = False,
+    win_private_assemblies = False,
+    cipher = block_cipher,
+    noarchive = False,
+)
+pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+exe = EXE(
+    pyz,
+    a.scripts,
+    a.binaries,
+    a.zipfiles,
+    a.datas,
+    [],
+    name = 'main',  # <可执行文件名>
+    debug = False,
+    bootloader_ignore_signals = False,
+    strip = False,
+    upx = True,
+    upx_exclude = [],
+    runtime_tmpdir = None,
+    console = True,
+)

+ 8 - 0
project-fastapi-hs/run-c.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+
+echo "Run-Commands(将py文件编译成so文件):" \
+&& set -x \
+&& cd /home/server/projects/taiwuict/cscec-8bur-vms/build/component-interface \
+&& python3 cythonize.py build_ext --inplace \
+&& find /home/server/projects/taiwuict/cscec-8bur-vms/build -name "*.py" -type f | xargs rm -rf \
+&& echo "End."

+ 10 - 0
project-fastapi-hs/run-exe-wrap.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+
+echo "Run-Commands(打包,打成可执行文件):" \
+&& set -x \
+&& cd /home/server/projects/taiwuict/cscec-8bur-vms/component-interface \
+&& pyinstaller main.spec \
+    --onefile \
+    --distpath . \
+&& tar -cf main.tar main \
+&& ./main

+ 6 - 0
project-fastapi-hs/run-exe.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+echo "Run-Commands(可执行文件方式运行):" \
+&& set -x \
+&& cd /home/server/projects/taiwuict/cscec-8bur-vms/component-interface \
+&& ./main

+ 7 - 0
project-fastapi-hs/run.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+#echo "BEGIN:" \
+#&& python3 api.py
+
+echo "BEGIN(源码方式运行):" \
+&& python3 main.py

+ 2 - 0
project-fastapi-hs/test/HingeCarPath.msg

@@ -0,0 +1,2 @@
+uint32 size
+Segment[] target_path

+ 284 - 0
project-fastapi-hs/test/SRI2024032514-机房端http接口文档.txt

@@ -0,0 +1,284 @@
+# --- test 获取token ---
+service_url = 'http://10.10.61.229:9000'
+url = f'{service_url}/token/api'
+data = {
+    'username': 'admin',
+    'password': 'admin',
+}
+response = requests.post(url=url, json=data)
+print(response.headers)
+"""
+{
+  'date': 'Thu, 04 Jan 2024 07:19:58 GMT',
+  'server': 'uvicorn',
+  # 登录token
+  'authorization': 'eyJhbGciOiJIUzUxMiIsImlhdCI6MTcwNDM1Mjc5OCwiZXhwIjoxNzA0NDM5MTk4fQ.eyJpZCI6IjY1NGUzNmI0NjBmZGE0M2UzOTI2YzNmYiIsInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6InBia2RmMjpzaGEyNTY6MTUwMDAwJGxEN1duR0hxJGUyNjhhNzgxOTFhYjdiNDE2ZDcxZjE1MDkxNzJjZWVkZWI3ZTNmNTM1ZGY0MGQ5NmQ4MzZlYmRjNTFmZGRmMzkifQ.IGi0lXezecP5AEjxgQ4lzQ5jyeYwDLoGmD7n29Q_1X6faSo4EKj4Q8A89BiAKBhjGAiNe7FSBFjmqhoZEXacUg',
+  'content-length': '107',
+  'content-type': 'application/json'
+}
+"""
+print(response.json())
+"""
+{
+  'code': 0  # 错误码 {0: 无异常}
+  'message': 'authorization passed.',
+  'uid': '654e36b460fda43e3926c3fb',  # 用户id
+  'role_name': '超级管理员',
+}
+"""
+
+# --- test 2001 获取全部渣包车状态 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 2001,  # 接口号
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': [
+    {
+      'uuid': 'aabbssff',  # 渣包车唯一标识
+      'name': '6号车',  # 别称
+      'status': 1  # 状态  # 1:离线 2:在线空闲 3: 人工驾驶中 4: 远程驾驶中 5: 自动驾驶中
+    }
+  ]
+}
+"""
+
+# --- test 2002 指定渣包车点火操作接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 2002,  # 接口号
+    'uuid': 'aassfafe',  # 渣包车唯一标识
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': 'aassfafe'
+}
+"""
+
+# --- test 2003 指定渣包车熄火操作接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 2003,  # 接口号
+    'uuid': 'aassfafe',  # 渣包车唯一标识
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': 'aassfafe'
+}
+"""
+
+# --- test 2004 指定渣包车建立远程操作权限(或是切换)接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 2004,  # 接口号
+    'uuid': 'aassfafe',  # 渣包车唯一标识
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': 'aassfafe'
+}
+"""
+
+# --- test 2005 开始作业接口(基于当前渣包车,选定作业内容) ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 2005,  # 接口号
+    'uuid': 'aassfafe',  # 渣包车唯一标识
+    'task_type': 'aassfafe',  # 作业类型  # 1: 叉包 2: 翻包 3:放包
+    'task_plan': [
+        {'name': 'L20', 'x': '54', 'y': '211'},
+        {'name': 'CD01端转弯圆心', 'x': '54', 'y': '211'},
+        {'name': '#6倒渣口', 'x': '54', 'y': '211'},
+    ],  # 路径规划  # name: 包位名称/倒渣口名称/转弯圆心点坐标 x: 坐标值 y: 坐标值
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': 'aassfafe'
+}
+"""
+
+# --- test 2101 新增渣包车接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 2101,  # 接口号
+    'name': '3号车',  # 别称
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': 'aassfafe'
+}
+"""
+
+# --- test 2102 修改渣包车接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 2102,  # 接口号
+    'uuid': '659765411dd2f1fe6d346b3b',  # 渣包车唯一标识
+    'name': '6号车',  # 别称
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+    'code': 0,
+    'data': {
+        'uuid': '659765411dd2f1fe6d346b3b',
+        'name': '6号车',
+        'update_at': 1704435941
+    }
+}
+"""
+
+# --- test 2103 删除渣包车接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 2103,  # 接口号
+    'uuid': '659764dd6b8e37c3ce95d849',  # 渣包车唯一标识
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+
+# --- test 3001 任务列表数据获取接口(分页) ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 3001,  # 接口号
+    'page': 1,  # 分页
+    'size': 10,  # 每页条数
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': [
+    {
+      'uuid': '659765411dd2f1fe6d346b3b',  # 任务id
+      'vehicle_uuid': '659765411dd2f1fe6d346b3b'  # 任务车辆id
+    }
+  ],
+  'page': 1,
+  'size': 10,
+  'total': 1  # 总条数
+}
+"""
+
+# --- test 3002 任务暂停接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 3002,  # 接口号
+    'uuid': 'aassfafe',  # 任务id
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': 'aassfafe'
+}
+"""
+
+# --- test 3003 任务取消接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 3003,  # 接口号
+    'uuid': 'aassfafe',  # 任务id
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': 'aassfafe'
+}
+"""
+
+# --- test 4001 获取全部渣包状态数据接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 4001,  # 接口号
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': {
+    '倒渣口-1': 1,
+    '倒渣口-2': 1,
+    '倒渣口-3': 1,
+    '倒渣口-4': 1,
+    '倒渣口-5': 1,
+    '倒渣口-6': 1,
+    '倒渣口-7': 1,
+    '包位-A-1': 1,
+    '包位-A-2': 1,
+    '包位-A-3': 1,
+    '包位-A-4': 1,
+    '包位-A-5': 1,
+    '包位-A-6': 1,
+    '包位-A-7': 1,
+    '包位-A-8': 1,
+    '包位-A-9': 1,
+    '包位-A-10': 1,
+    '包位-A-11': 1,
+    '包位-A-12': 1,
+    '包位-A-13': 1,
+    '包位-A-14': 1,
+    '包位-A-15': 1,
+    '包位-A-16': 1,
+    '包位-A-17': 1,
+    '包位-A-18': 1,
+    '包位-A-19': 1,
+    '包位-A-20': 1,
+    '包位-A-21': 1,
+    '包位-A-22': 1,
+    '包位-A-23': 1,
+    '包位-A-24': 1,
+    '包位-A-25': 1,
+    '包位-A-26': 1,
+    '包位-A-27': 1,
+    '包位-A-28': 1,
+    '包位-A-29': 1,
+    '包位-A-30': 1,
+    '包位-A-31': 1,
+  }
+}
+"""
+
+# --- test 5001 获取告警数据列表接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 5001,  # 接口号
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': [
+    {
+      'message': '1号车油量低,请及时加油!',  # 警告内容
+      'create_at': 1704435199  # 警告时间(时间戳)
+    }
+  ]
+}
+"""

+ 32 - 0
project-fastapi-hs/test/SRI2024032514-车端http接口文档.txt

@@ -0,0 +1,32 @@
+#车辆状态更新
+mqtt服务地址:192.168.131.23
+mqtt服务端口:41883
+mqtt话题:hs/vehicle/state
+mqtt消息及注释:
+{
+    "ip": "192.168.131.180",  # 车辆ip
+    "state": 1,  # 车辆状态 1 离线 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+    "direction": 15,  # 车头方向(场地坐标偏转角度)
+}
+
+#渣包位置更新
+mqtt服务地址:192.168.131.23
+mqtt服务端口:41883
+mqtt话题:hs/pot/point
+mqtt消息(json字符串):{"pot": "n32", "point": null}
+mqtt消息注释:
+{
+  "pot": "n32",  # 渣包标识
+  "point": null   # 位置坐标
+}
+
+#渣包位置更新
+mqtt服务地址:192.168.131.23
+mqtt服务端口:41883
+mqtt话题:hs/pot/point
+mqtt消息(json字符串):{"pot": "n32", "point": null}
+mqtt消息注释:
+{
+  "pot": "n32",  # 渣包标识
+  "point": null   # 位置坐标
+}

+ 8 - 0
project-fastapi-hs/test/Segment.msg

@@ -0,0 +1,8 @@
+geometry_msgs/Point start_point  # 
+geometry_msgs/Point end_point
+geometry_msgs/Point center_point
+float32 max_vel
+uint8 LINE = 1            # LINE target
+uint8 CIRCLE = 2          # CIRCLE target
+####
+uint8 segment_type

+ 14 - 0
project-fastapi-hs/test/Segment.py

@@ -0,0 +1,14 @@
+# 导入所需的消息类型
+from geometry_msgs.msg import Point
+
+# 创建一个 Point 对象,并设置其 x、y、z 坐标值
+point = Point()
+point.x = 1.0
+point.y = 2.0
+point.z = 3.0
+
+# 打印 Point 对象的内容
+print("Point: ", point)
+print("X coordinate: ", point.x)
+print("Y coordinate: ", point.y)
+print("Z coordinate: ", point.z)

+ 136 - 0
project-fastapi-hs/test/test-2000.py

@@ -0,0 +1,136 @@
+import requests
+
+# --- test 获取token ---
+# url = 'http://10.10.61.229:9000/token/api'
+url = 'http://192.168.131.23:9000/token/api'
+data = {
+    'username': 'admin',  # 用户名
+    'password': 'admin',  # 密码
+}
+response = requests.post(url=url, json=data)
+code = response.json().get('code')
+token = response.headers.get('authorization')
+
+# --- test 2001 获取全部渣包车状态 ---
+url = 'http://192.168.131.23:9000/v5/api'
+data = {
+    'code': 2001,  # 接口号
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': [
+    {
+      'name': '3号车',  # 车辆名称
+      'address': '192.168.131.180',  # 车辆ip
+      'check_vehicle_direction': True,  # 当前车头方向是否符合自动驾驶条件 True 符合 False 不符合(默认值)
+      'state': 2,  # 车辆状态 1 离线(默认值) 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+      'current_vehicle_direction': 9,  # 当前车头方向类型 3 围栏方向 9 渣场方向 12 维修间方向 6 维修间正对方向 0 未知(默认值)
+      'current_vehicle_weight': 0,  # 当前车辆负载重量
+      'uuid': '65faa9d5921aec72550fb32d'  # 车辆标识
+    },
+    {
+      'name': '5号车',
+      'address': '192.168.131.xxx',
+      'check_vehicle_direction': False,
+      'current_vehicle_direction': 0,
+      'state': 1,
+      'current_vehicle_weight': 0,
+      'uuid': '65faa9d5921aec72550fb32e'
+    }
+  ]
+}
+"""
+
+# --- test 2002 指定渣包车点火操作接口 ---
+# url = 'http://10.10.61.229:9000/v5/api'
+# data = {
+#     'code': 2002,  # 接口号
+#     'uuid': 'aassfafe',  # 渣包车唯一标识
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'data': 'aassfafe'
+# }
+# """
+
+# --- test 2003 指定渣包车熄火操作接口 ---
+# url = 'http://10.10.61.229:9000/v5/api'
+# data = {
+#     'code': 2003,  # 接口号
+#     'uuid': 'aassfafe',  # 渣包车唯一标识
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'data': 'aassfafe'
+# }
+# """
+
+# --- test 2004 指定渣包车建立远程操作权限(或是切换)接口 ---
+# url = 'http://10.10.61.229:9000/v5/api'
+# data = {
+#     'code': 2004,  # 接口号
+#     'uuid': 'aassfafe',  # 渣包车唯一标识
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'data': 'aassfafe'
+# }
+# """
+
+# --- test 2101 新增渣包车接口 ---
+# url = 'http://10.10.61.229:9000/v5/api'
+# data = {
+#     'code': 2101,  # 接口号
+#     'name': '3号车',  # 车辆名称
+#     'address': '192.168.131.41',  # 网络地址
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'data': 'aassfafe'
+# }
+# """
+
+# --- test 2102 修改渣包车接口 ---
+# url = 'http://10.10.61.229:9000/v5/api'
+# data = {
+#     'code': 2102,  # 接口号
+#     'uuid': '659765411dd2f1fe6d346b3b',  # 渣包车唯一标识
+#     'name': '3号车',  # 车辆名称
+#     'address': '192.168.131.41',  # 网络地址
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#     'code': 0,
+#     'data': {
+#         'uuid': '659765411dd2f1fe6d346b3b',
+#         'name': '6号车',
+#         'update_at': 1704435941
+#     }
+# }
+# """
+
+# --- test 2103 删除渣包车接口 ---
+# url = 'http://10.10.61.229:9000/v5/api'
+# data = {
+#     'code': 2103,  # 接口号
+#     'uuid': '659764dd6b8e37c3ce95d849',  # 渣包车唯一标识
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())

+ 95 - 0
project-fastapi-hs/test/test-3000.py

@@ -0,0 +1,95 @@
+import requests
+
+# --- test 获取token ---
+# url = 'http://10.10.61.229:9000/token/api'
+url = 'http://192.168.131.23:9000/token/api'
+data = {
+    'username': 'admin',  # 用户名
+    'password': 'admin',  # 密码
+}
+response = requests.post(url=url, json=data)
+code = response.json().get('code')
+token = response.headers.get('authorization')
+
+# --- test 3001 任务列表数据获取接口(分页) ---
+# --- test 3001 任务列表数据获取接口(分页) ---
+# url = 'http://192.168.131.23:9000/v5/api'
+# data = {
+#     'code': 3001,  # 接口号
+#     'page': 1,  # 分页
+#     'size': 10,  # 每页条数
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'data': [
+#     {
+#       'vehicle_uuid': '65faa9d5921aec72550fb32d',  # 车辆id
+#       'task_type': 101,  # 任务类型 101 自动驾驶 102 叉包 103 放包 104 倒渣
+#       'target_point_name': 'F.20',  # 目标点名称
+#       'task_state': 1,  # 任务状态 1 已经下发 2 已完成 3 中止 4 失败
+#       'create_at': 1711526546,  # 创建时间
+#       'uuid': '6603d2922276884b00caa06c'  # 任务id
+#     }
+#   ],
+#   'total': 1,
+#   'page': 1,
+#   'size': 10
+# }
+# """
+
+# --- test 3003 任务取消接口 ---
+# url = 'http://10.10.61.229:9000/v5/api'
+# url = 'http://192.168.131.23:9000/v5/api'
+# data = {
+#     'code': 3003,  # 接口号
+#     'uuid': '659765411dd2f1fe6d346b3b',  # 任务id
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'data': '659765411dd2f1fe6d346b3b'
+# }
+# """
+
+# --- test 3004 任务创建并执行 ---
+# url = 'http://192.168.131.23:9000/v5/api'
+# data = {
+#     'code': 3004,  # 接口号
+#     'uuid': '65faa9d5921aec72550fb32d',  # 渣包车唯一标识
+#     'task_type': 101,  # 任务类型 101 自动驾驶 102 叉包 103 放包 104 倒渣
+#     'target_point_name': 'F.20',  # 目标点名称
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'data': '659765411dd2f1fe6d346b3b'  # 任务id
+# }
+# """
+
+# --- test 3005 获取指定任务信息 ---
+url = 'http://192.168.131.23:9000/v5/api'
+data = {
+    'code': 3005,  # 接口号
+    'uuid': '6603d2922276884b00caa06c',  # 任务id
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': {
+    'task_type': 101,  # 任务类型 101 自动驾驶 102 叉包 103 放包 104 倒渣
+    'target_point_name': 'F.20',  # 目标点
+    'task_state': 1,  # 任务状态 1 已经下发 2 已完成 3 中止 4 失败
+    'create_at': 1711381601,  # 创建时间
+    'uuid': '66019c617b5e102e42bc363a'  # 任务id
+  }
+}
+"""

+ 33 - 0
project-fastapi-hs/test/test-4000.py

@@ -0,0 +1,33 @@
+import requests
+
+# --- test 获取token ---
+# url = 'http://10.10.61.229:9000/token/api'
+url = 'http://192.168.131.23:9000/token/api'
+data = {
+    'username': 'admin',  # 用户名
+    'password': 'admin',  # 密码
+}
+response = requests.post(url=url, json=data)
+code = response.json().get('code')
+token = response.headers.get('authorization')
+
+# --- test 4001 获取全部渣包状态数据接口 ---
+# url = 'http://10.10.61.229:9000/v5/api'
+url = 'http://192.168.131.23:9000/v5/api'
+data = {
+    'code': 4001,  # 接口号
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': [
+    {
+      'pot_name': 'A.1',  # 渣罐别称
+      'pot_status': 4,  # 渣罐状态 1 空位 2 就绪 3 缓冷(空冷) 4 水冷 5 自冷(水冷) 6 待倒 7 故障
+      'pot_number': '1'  # 渣罐编号
+    }
+  ]
+}
+"""

+ 32 - 0
project-fastapi-hs/test/test-5000.py

@@ -0,0 +1,32 @@
+import requests
+
+# --- test 获取token ---
+url = 'http://10.10.61.229:9000/token/api'
+data = {
+    'username': 'admin',  # 用户名
+    'password': 'admin',  # 密码
+}
+response = requests.post(url=url, json=data)
+code = response.json().get('code')
+token = response.headers.get('authorization')
+# print(code, token)
+
+# --- test 5001 获取告警数据列表接口 ---
+# --- test 5001 获取告警数据列表接口 ---
+url = 'http://10.10.61.229:9000/v5/api'
+data = {
+    'code': 5001,  # 接口号
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': [
+    {
+      'message': '1号车油量低,请及时加油!',  # 警告内容
+      'create_at': 1704435199  # 警告时间(时间戳)
+    }
+  ]
+}
+"""

+ 4 - 0
project-fastapi-hs/test/test-enfei.py

@@ -0,0 +1,4 @@
+import requests
+
+# --- test 获取渣罐信息 --- | 10.62.115.11 hyty-ifactory.platform.dyys.com > /etc/hosts
+print(requests.get(url='http://hyty-ifactory.platform.dyys.com/gongyi/ui/ResiduePackage/driver/getParamsSet').json())

+ 30 - 0
project-fastapi-hs/test/test_key_api.py

@@ -0,0 +1,30 @@
+import requests
+
+# --- test 获取token ---
+# url = 'http://10.10.61.229:9000/token/api'
+url = 'http://58.34.94.177:29000/token/api'
+data = {
+    'username': 'admin',
+    'password': 'admin',
+}
+response = requests.post(url=url, json=data)
+print(response.headers)
+"""
+{
+  'date': 'Thu, 04 Jan 2024 07:19:58 GMT',
+  'server': 'uvicorn',
+  # 登录token
+  'authorization': 'eyJhbGciOiJIUzUxMiIsImlhdCI6MTcwNDM1Mjc5OCwiZXhwIjoxNzA0NDM5MTk4fQ.eyJpZCI6IjY1NGUzNmI0NjBmZGE0M2UzOTI2YzNmYiIsInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6InBia2RmMjpzaGEyNTY6MTUwMDAwJGxEN1duR0hxJGUyNjhhNzgxOTFhYjdiNDE2ZDcxZjE1MDkxNzJjZWVkZWI3ZTNmNTM1ZGY0MGQ5NmQ4MzZlYmRjNTFmZGRmMzkifQ.IGi0lXezecP5AEjxgQ4lzQ5jyeYwDLoGmD7n29Q_1X6faSo4EKj4Q8A89BiAKBhjGAiNe7FSBFjmqhoZEXacUg',
+  'content-length': '107',
+  'content-type': 'application/json'
+}
+"""
+print(response.json())
+"""
+{
+  'code': 0  # 错误码 {0: 无异常}
+  'message': 'authorization passed.',
+  'uid': '654e36b460fda43e3926c3fb',  # 登录用户id
+  'role_name': '超级管理员',
+}
+"""

+ 15 - 0
project-fastapi-hs/test/test_sanyi.py

@@ -0,0 +1,15 @@
+# json_data = [
+#     {
+#         'name': '#1',  # 倒渣口名称
+#         'state': 1,  # 倒渣口状态 (1: 可用 2: 不可用)
+#     }, {
+#         'name': '#2',  # 倒渣口名称
+#         'state': 1,  # 倒渣口状态 (1: 可用 2: 不可用)
+#     }, {
+#         'name': '#3',  # 倒渣口名称
+#         'state': 1,  # 倒渣口状态 (1: 可用 2: 不可用)
+#     }, {
+#         'name': '#4',  # 倒渣口名称
+#         'state': 1,  # 倒渣口状态 (1: 可用 2: 不可用)
+#     }..
+# ]

+ 30 - 0
project-fastapi-hs/test/test_token.py

@@ -0,0 +1,30 @@
+import requests
+
+# --- test 获取token ---
+service_url = 'http://10.10.61.229:9000'
+url = f'{service_url}/token/api'
+data = {
+    'username': 'admin',
+    'password': 'admin',
+}
+response = requests.post(url=url, json=data)
+print(response.headers)
+"""
+{
+  'date': 'Thu, 04 Jan 2024 07:19:58 GMT',
+  'server': 'uvicorn',
+  # 登录token
+  'authorization': 'eyJhbGciOiJIUzUxMiIsImlhdCI6MTcwNDM1Mjc5OCwiZXhwIjoxNzA0NDM5MTk4fQ.eyJpZCI6IjY1NGUzNmI0NjBmZGE0M2UzOTI2YzNmYiIsInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6InBia2RmMjpzaGEyNTY6MTUwMDAwJGxEN1duR0hxJGUyNjhhNzgxOTFhYjdiNDE2ZDcxZjE1MDkxNzJjZWVkZWI3ZTNmNTM1ZGY0MGQ5NmQ4MzZlYmRjNTFmZGRmMzkifQ.IGi0lXezecP5AEjxgQ4lzQ5jyeYwDLoGmD7n29Q_1X6faSo4EKj4Q8A89BiAKBhjGAiNe7FSBFjmqhoZEXacUg',
+  'content-length': '107',
+  'content-type': 'application/json'
+}
+"""
+print(response.json())
+"""
+{
+  'code': 0  # 错误码 {0: 无异常}
+  'message': 'authorization passed.',
+  'uid': '654e36b460fda43e3926c3fb',  # 登录用户id
+  'role_name': '超级管理员',
+}
+"""

+ 264 - 0
project-fastapi-hs/unit/Scheduler_a1.py

@@ -0,0 +1,264 @@
+"""
+
+"""
+
+# 圆心点坐标字典 todo 关于入弯和出弯 是否是固定的
+d1 = {
+    'LM.head': {'center': (11, 22), 'center_12': (11, 21), 'center_9': (11, 21), 'center_6': (11, 21)},
+    'JK.head': {'center': (11, 22), 'center_12': (11, 21), 'center_9': (11, 21), 'center_6': (11, 21)},
+    'HI.head': {'center': (11, 22), 'center_12': (11, 21), 'center_9': (11, 21), 'center_6': (11, 21)},
+    'FG.head': {'center': (11, 22), 'center_12': (11, 21), 'center_9': (11, 21), 'center_6': (11, 21)},
+    'DE.head': {'center': (11, 22), 'center_12': (11, 21), 'center_9': (11, 21), 'center_6': (11, 21)},
+    'BC.head': {'center': (11, 22), 'center_12': (11, 21), 'center_9': (11, 21), 'center_6': (11, 21)},
+    'A.head': {'center': (11, 22), 'center_12': (11, 21), 'center_9': (11, 21), 'center_6': (11, 21)},
+
+    'LM.tail': {'center': (11, 22), 'center_12': (11, 21), 'center_3': (11, 21), 'center_6': (11, 21)},
+    'JK.tail': {'center': (11, 22), 'center_12': (11, 21), 'center_3': (11, 21), 'center_6': (11, 21)},
+    'HI.tail': {'center': (11, 22), 'center_12': (11, 21), 'center_3': (11, 21), 'center_6': (11, 21)},
+    'FG.tail': {'center': (11, 22), 'center_12': (11, 21), 'center_3': (11, 21), 'center_6': (11, 21)},
+    'DE.tail': {'center': (11, 22), 'center_12': (11, 21), 'center_3': (11, 21), 'center_6': (11, 21)},
+    'BC.tail': {'center': (11, 22), 'center_12': (11, 21), 'center_3': (11, 21), 'center_6': (11, 21)},
+    'A.tail': {'center': (11, 22), 'center_12': (11, 21), 'center_3': (11, 21), 'center_6': (11, 21)},
+
+}
+
+# 倒渣口坐标字典
+d2 = {
+    'dump.MN': {'dump': (11, 205.75)},  # 1号倒渣口
+    'dump.KL': {'dump': (11, 177.75)},  # 2号倒渣口
+    'dump.IJ': {'dump': (11, 149.75)},  # 3号倒渣口
+    'dump.GH': {'dump': (11, 121.75)},  # 4号倒渣口
+    'dump.EF': {'dump': (11, 93.75)},  # 5号倒渣口
+    'dump.CD': {'dump': (11, 65.75)},  # 6号倒渣口
+    'dump.AB': {'dump': (11, 37.75)},  # 7号倒渣口
+}
+
+# 接渣口坐标字典
+d3 = {
+    'load.1': {'load': (11, 22)},
+    'load.2': {'load': (11, 22)},
+    'load.3': {'load': (11, 22)},
+}
+
+# 停车区坐标字典
+d5 = {
+    'pack.1': {'x': 77, 'y': 44},
+    'pack.2': {'x': 77, 'y': 44},
+}
+
+# 渣罐坐标字典
+d4 = {
+    'M.31': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.30': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.29': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.28': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.27': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.26': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.25': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.24': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.23': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.22': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.21': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.20': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.19': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.18': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.17': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.16': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.15': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.14': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.13': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.12': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.11': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.10': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.9': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.8': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.7': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.6': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.5': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.4': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.3': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.2': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.1': {'pot': (22, 33), 'road_center': (44, 55)},
+
+    'F.31': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.30': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.29': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.28': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.27': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.26': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.25': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.24': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.23': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.22': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.21': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.20': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.19': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.18': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.17': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.16': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.15': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.14': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.13': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.12': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.11': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.10': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.9': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.8': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.7': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.6': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.5': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.4': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.3': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.2': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.1': {'pot': (22, 33), 'road_center': (44, 55)},
+
+}
+
+
+def get_nearest_point_from_road_center_point(x, y):
+    """
+    获取当前所在位置最接近的坐标位置
+    todo 判断是否是合理的范围,逻辑上移 offset = 11  # 允许范围(单位:米)
+    """
+    x = float(x)
+    y = float(y)
+    min_distance = float('inf')  # 初始值无穷大
+    for k, v in d4.items():
+        center_x = float(v.get('road_center')[0])
+        center_y = float(v.get('road_center')[1])
+        distance = ((center_x - x) ** 2 + (center_y - y) ** 2) ** (1 / 2)
+
+        if distance < min_distance:
+            min_distance = distance
+            nearest_point = center_x, center_y
+
+    return nearest_point
+
+
+def get_center_point_by_keyword(w1='N', w2='head'):
+    """
+    获取圆心点坐标
+    w1: 关键字
+    w2: 关键字
+    """
+    # d1 = todo 从json文件中获取
+    for k, v in d1.items():
+        k_s1, k_s2 = k.split('.')
+        if w1 not in k_s1:
+            continue
+        if w2 not in k_s2:
+            continue
+        return dict(**v, **{'name': k})
+
+
+def get_path_name_by_pot_name(pot_name='M.25'):
+    """根据渣罐名获取道路名"""
+    s1, s2 = pot_name.split('.')
+    path_name_list = [
+        'MN',
+        'KL',
+        'IJ',
+        'GH',
+        'EF',
+        'CD',
+        'AB',
+    ]
+    for s in path_name_list:
+        if s1 in s:
+            return s
+
+
+import json
+
+
+def test(s_direction='head', s_name='M.10', e_name='M.1'):
+    """
+    s_direction: 起始方向  head 渣场方向 tail 围栏方向
+    s_name: 起始位置
+    e_name: 目标位置
+    """
+    # --- update d4 ---
+    data = json.load(open('../data/渣罐坐标数据.json', 'r'))
+    for item in data.get('pot_name|pot_x|pot_y|road_center_x|road_center_y'):
+        pot_name, pot_x, pot_y, road_center_x, road_center_y = item.split('|')
+        d4[pot_name] = {
+            'pot': (pot_x, pot_y),
+            'road_center': (road_center_x, road_center_y),
+        }
+
+    # --- test
+    # x, y = get_nearest_point_from_road_center_point(x=112.5, y=27.25)
+    # return x, y
+
+    # --- check ---
+    if s_direction not in ['head', 'tail']:
+        return {'code': 1, 'data': []}
+
+    # --- check ---
+    if not s_name or not e_name:
+        return {'code': 2, 'data': []}
+
+    # --- prepare ---
+    if 'dump' in s_name:
+        s_point_item = dict(**d2.get(s_name), **{'name': s_name})
+    else:
+        s_point_item = dict(**d4.get(s_name), **{'name': s_name})
+
+    # --- prepare ---
+    if 'dump' in e_name:
+        e_point_item = dict(**d2.get(e_name), **{'name': e_name})
+    else:
+        e_point_item = dict(**d4.get(e_name), **{'name': e_name})
+
+    # --- check --- 判断是否是在正前方的情况1
+    if s_direction == 'head' and 'dump' not in s_name and 'dump' not in e_name:
+        s_s1, s_s2 = s_name.split('.')
+        e_s1, e_s2 = e_name.split('.')
+        if s_s1 == e_s1 and int(e_s2) < int(s_s2):
+            return {'code': 0, 'data': [s_point_item, e_point_item]}
+
+    # --- check --- 判断是否是在正前方的情况2
+    if s_direction == 'tail' and 'dump' not in s_name and 'dump' not in e_name:
+        s_s1, s_s2 = s_name.split('.')
+        e_s1, e_s2 = e_name.split('.')
+        if s_s1 == e_s1 and int(s_s2) < int(e_s2):
+            return {'code': 0, 'data': [s_point_item, e_point_item]}
+
+    # --- check --- head方向,向下行驶,并拐弯情况(共2个弯,1个出弯,1个入弯)
+    if s_direction == 'head' and 'dump' not in s_name and 'dump' not in e_name:
+        s_s1, s_s2 = s_name.split('.')
+        e_s1, e_s2 = e_name.split('.')
+        if s_s1 != e_s1:
+            w1 = get_path_name_by_pot_name(s_name)[:1]  # 第一位
+            center_point_item_1 = get_center_point_by_keyword(w1=w1, w2='head')
+            w1 = get_path_name_by_pot_name(e_name)[-1:]  # 最后位
+            center_point_item_2 = get_center_point_by_keyword(w1=w1, w2='head')
+            return {'code': 0, 'data': [s_point_item, center_point_item_1, center_point_item_2, e_point_item]}
+
+    # --- check --- head方向,向下行驶,从包位到倒渣口(共1个弯,1个出弯)
+    if s_direction == 'head' and 'dump' not in s_name and 'dump' in e_name:
+        pass
+
+    # --- check --- head方向,向下行驶,从倒渣口到包位(共1个弯,1个入弯)
+    if s_direction == 'head' and 'dump' in s_name and 'dump' not in e_name:
+        pass
+
+    # --- check --- tail方向,向上行驶,从倒渣口到包位(共1个弯,1个入弯)
+    if s_direction == 'tail' and 'dump' in s_name and 'dump' not in e_name:
+        pass
+
+    # --- check --- tail方向,向上行驶,从倒渣口到包位(共1个弯,1个入弯)
+    if s_direction == 'tail' and 'dump' in s_name and 'dump' not in e_name:
+        pass
+
+
+if __name__ == '__main__':
+    # --- test ---
+    # 判断是否是在正前方的情况1
+    # out = test(s_direction='head', s_name='M.10', e_name='M.1')
+    # 判断是否是在正前方的情况2
+    # out = test(s_direction='tail', s_name='M.12', e_name='M.20')
+    # head方向,向下行驶,并拐弯情况(共2个弯,1个是出弯,1个是入弯)
+    out = test(s_direction='head', s_name='M.12', e_name='F.20')
+    print(out)
+

+ 434 - 0
project-fastapi-hs/unit/Scheduler_b1.py

@@ -0,0 +1,434 @@
+"""
+
+"""
+
+# 圆心点坐标字典
+d1 = {
+    'LM.head': {'center': (11, 22)},
+    'JK.head': {'center': (11, 22)},
+    'HI.head': {'center': (11, 22)},
+    'FG.head': {'center': (11, 22)},
+    'DE.head': {'center': (11, 22)},
+    'BC.head': {'center': (11, 22)},
+    'A.head': {'center': (11, 22)},
+
+    'LM.tail': {'center': (11, 22)},
+    'JK.tail': {'center': (11, 22)},
+    'HI.tail': {'center': (11, 22)},
+    'FG.tail': {'center': (11, 22)},
+    'DE.tail': {'center': (11, 22)},
+    'BC.tail': {'center': (11, 22)},
+    'A.tail': {'center': (11, 22)},
+
+}
+
+# 倒渣口坐标字典
+d2 = {
+    'dump.MN': {'point': (11, 205.75)},  # 1号倒渣口
+    'dump.KL': {'point': (11, 177.75)},  # 2号倒渣口
+    'dump.IJ': {'point': (11, 149.75)},  # 3号倒渣口
+    'dump.GH': {'point': (11, 121.75)},  # 4号倒渣口
+    'dump.EF': {'point': (11, 93.75)},  # 5号倒渣口
+    'dump.CD': {'point': (11, 65.75)},  # 6号倒渣口
+    'dump.AB': {'point': (11, 37.75)},  # 7号倒渣口
+}
+
+# 接渣口坐标字典
+d3 = {
+    'load.1': {'point': (11, 22)},
+    'load.2': {'point': (11, 22)},
+    'load.3': {'point': (11, 22)},
+}
+
+# 停车区坐标字典
+d5 = {
+    'pack.1': {'x': 77, 'y': 44},
+    'pack.2': {'x': 77, 'y': 44},
+}
+
+# 渣罐坐标字典
+d4 = {
+    'M.31': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.30': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.29': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.28': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.27': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.26': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.25': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.24': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.23': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.22': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.21': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.20': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.19': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.18': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.17': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.16': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.15': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.14': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.13': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.12': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.11': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.10': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.9': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.8': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.7': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.6': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.5': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.4': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.3': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.2': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.1': {'pot': (22, 33), 'road_center': (44, 55)},
+
+    'F.31': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.30': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.29': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.28': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.27': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.26': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.25': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.24': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.23': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.22': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.21': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.20': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.19': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.18': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.17': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.16': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.15': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.14': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.13': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.12': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.11': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.10': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.9': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.8': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.7': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.6': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.5': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.4': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.3': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.2': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.1': {'pot': (22, 33), 'road_center': (44, 55)},
+
+}
+
+
+def get_nearest_point_name_from_pot_point(x, y):
+    """
+    获取当前位置距离最忌的罐体位置名称
+    """
+    x = float(x)
+    y = float(y)
+    min_distance = float('inf')  # 初始值无穷大
+    for k, v in d4.items():
+        center_x = float(v.get('pot')[0])
+        center_y = float(v.get('pot')[1])
+        distance = ((center_x - x) ** 2 + (center_y - y) ** 2) ** (1 / 2)
+
+        if distance < min_distance:
+            min_distance = distance
+            nearest_pot_name = k
+
+    return nearest_pot_name
+
+
+def get_nearest_point_from_road_center_point(x, y):
+    """
+    获取当前所在位置最接近的坐标位置
+    """
+    x = float(x)
+    y = float(y)
+    min_distance = float('inf')  # 初始值无穷大
+    for k, v in d4.items():
+        center_x = float(v.get('road_center')[0])
+        center_y = float(v.get('road_center')[1])
+        distance = ((center_x - x) ** 2 + (center_y - y) ** 2) ** (1 / 2)
+
+        if distance < min_distance:
+            min_distance = distance
+            nearest_point = center_x, center_y
+
+    return nearest_point
+
+
+def get_center_point_by_keyword(w1='N', w2='head'):
+    """
+    获取圆心点坐标
+    w1: 关键字
+    w2: 关键字
+    """
+    for k, v in d1.items():
+        k_s1, k_s2 = k.split('.')
+        if w1 not in k_s1:
+            continue
+        if w2 not in k_s2:
+            continue
+        return dict(**v, **{'name': k})
+
+
+def get_path_name_by_pot_name(pot_name='M.25'):
+    """根据渣罐名获取道路名"""
+    s1, s2 = pot_name.split('.')
+    path_name_list = [
+        'MN',
+        'KL',
+        'IJ',
+        'GH',
+        'EF',
+        'CD',
+        'AB',
+    ]
+    for s in path_name_list:
+        if s1 in s:
+            return s
+
+
+def get_path_name_by_dump_name(dump_name='dump.MN'):
+    """根据渣罐名获取道路名"""
+    _, s2 = dump_name.split('.')
+    return s2
+
+
+import json
+
+
+def test(current_direction_type=3, start_x=11, start_y=22, target_point_name='M.1'):
+    """
+    current_direction_type: 起始方向  3 围栏方向 9 渣场方向 12 维修间方向 6 维修间正对方向 0 未知(默认值)
+    s_name: 起始位置
+    e_name: 目标位置
+    """
+    # --- update d4 ---
+    # data = json.load(open('../data/渣罐坐标数据.json', 'r'))
+    # data = json.load(open('/home/ubuntu/repositories/repositories/casperz.py-project/project-fastapi-hs/data/渣罐坐标数据.json', 'r'))
+    data = json.load(
+        open(r'E:\casper\repositories\repositories\casperz.py-project\project-fastapi-hs\data\渣罐坐标数据.json', 'rb'))
+    for item in data.get('pot_name|pot_x|pot_y|road_center_x|road_center_y'):
+        pot_name, pot_x, pot_y, road_center_x, road_center_y = item.split('|')
+        d4[pot_name] = {
+            'pot': (pot_x, pot_y),
+            'road_center': (road_center_x, road_center_y),
+        }
+
+    # --- update d2 ---
+    # data = json.load(open('../data/渣罐坐标数据.json', 'r'))
+    # data = json.load(open('/home/ubuntu/repositories/repositories/casperz.py-project/project-fastapi-hs/data/倒渣口坐标数据.json', 'r'))
+    data = json.load(
+        open(r'E:\casper\repositories\repositories\casperz.py-project\project-fastapi-hs\data\倒渣口坐标数据.json',
+             'rb'))
+    for item in data.get('name|x|y'):
+        name, x, y = item.split('|')
+        d2[name] = {
+            'point': (x, y),
+        }
+
+    # --- check ---
+    if current_direction_type not in [3, 9]:
+        return 1  # 暂不支持
+
+    # --- check ---
+    if not start_x or not start_y:
+        return 2  # 参数缺失
+
+    # --- define ---
+    current_point_name = get_nearest_point_name_from_pot_point(start_x, start_y)
+
+    # --- define ---
+    if 'dump' in current_point_name:
+        s_point_item = dict(**d2.get(current_point_name), **{'name': current_point_name})
+    else:
+        s_point_item = dict(**d4.get(current_point_name), **{'name': current_point_name})
+
+    # --- define ---
+    if 'dump' in target_point_name:
+        e_point_item = dict(**d2.get(target_point_name), **{'name': target_point_name})
+    else:
+        e_point_item = dict(**d4.get(target_point_name), **{'name': target_point_name})
+
+    # --- define ---
+    radius = 14  # 转弯半径
+    s_s1, s_s2 = current_point_name.split('.')
+    e_s1, e_s2 = target_point_name.split('.')
+    print(f"current_point_name: {current_point_name}")
+    print(f"target_point_name: {target_point_name}")
+
+    # --- check ---
+    if 'dump' not in current_point_name and 'dump' not in target_point_name:
+        if current_direction_type == 9 and s_s1 == e_s1 and int(s_s2) > int(e_s2):
+            """
+            情况1:判断是否是在正前方的情况1
+            """
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        {
+                            # 直行轨迹坐标参数
+                            'start_point_x': start_x,
+                            'start_point_y': start_y,
+                            'end_point_x': e_point_item.get('road_center')[0],
+                            'end_point_y': e_point_item.get('road_center')[1],
+                        },
+                    ],
+                }
+            ]
+        elif current_direction_type == 3 and s_s1 == e_s1 and int(s_s2) < int(e_s2):
+            """
+            情况2:判断是否是在正前方的情况2
+            """
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': start_x,
+                            'start_point_y': start_y,
+                            'end_point_x': e_point_item.get('road_center')[0],
+                            'end_point_y': e_point_item.get('road_center')[1],
+                        },
+                    ],
+                }
+            ]
+        elif current_direction_type == 9 and s_s1 > e_s1:
+            """
+            情况3:车头9方向,向6点行驶,并拐弯情况(共2个弯,1个是出弯,1个是入弯)
+            """
+            w1 = get_path_name_by_pot_name(current_point_name)[:1]  # 首位
+            center_1 = get_center_point_by_keyword(w1=w1, w2='head')
+            w1 = get_path_name_by_pot_name(target_point_name)[-1:]  # 末位
+            center_2 = get_center_point_by_keyword(w1=w1, w2='head')
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': start_x,  # 起始点坐标
+                            'start_point_y': start_y,  # 起始点坐标
+                            'end_point_x': center_1.get('center')[0],  # 入弯坐标
+                            'end_point_y': center_1.get('center')[1] + radius,  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_1.get('center')[0],  # 入弯坐标
+                            "start_point_y": center_1.get('center')[1] + radius,  # 入弯坐标
+                            "center_point_x": center_1.get('center')[0],  # 圆心坐标
+                            "center_point_y": center_1.get('center')[1],  # 圆心坐标
+                            "end_point_x": center_1.get('center')[0] - radius,  # 出弯坐标
+                            "end_point_y": center_1.get('center')[1],  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_1.get('center')[0] - radius,  # 出弯坐标
+                            'start_point_y': center_1.get('center')[1],  # 出弯坐标
+                            'end_point_x': center_2.get('center')[0] - radius,  # 入弯坐标
+                            'end_point_y': center_2.get('center')[1],  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_2.get('center')[0] - radius,  # 入弯坐标
+                            "start_point_y": center_2.get('center')[1],  # 入弯坐标
+                            "center_point_x": center_2.get('center')[0],  # 圆心坐标
+                            "center_point_y": center_2.get('center')[1],  # 圆心坐标
+                            "end_point_x": center_2.get('center')[0],  # 出弯坐标
+                            "end_point_y": center_2.get('center')[1] - radius,  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_2.get('center')[0],  # 出弯坐标
+                            'start_point_y': center_2.get('center')[1] - radius,  # 出弯坐标
+                            'end_point_x': e_point_item.get('road_center')[0],  # 目标点坐标
+                            'end_point_y': e_point_item.get('road_center')[1],  # 目标点坐标
+                        },
+                    ],
+                }
+            ]
+
+    elif 'dump' not in current_point_name and 'dump' in target_point_name:
+        if current_direction_type == 9:
+            """
+            情况4:渣罐位,车头9点方向,6点拐弯,3点拐弯,倒车倒入指定倒渣口
+            """
+            path_name = get_path_name_by_pot_name(current_point_name)
+            # print(f"path_name: {path_name}")
+            corner_name = path_name[:1]  # 拐弯点(9点到6点方向转弯)
+            center_1 = get_center_point_by_keyword(w1=corner_name, w2='head')
+            # print(f"center_1: {center_1}")
+            path_name = get_path_name_by_dump_name(target_point_name)
+            corner_name = path_name[-1:]  # 拐弯点(6点到3点方向转弯)
+            center_2 = get_center_point_by_keyword(w1=corner_name, w2='head')
+            # print(f"center_2: {center_2}")
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': start_x,  # 起始点坐标
+                            'start_point_y': start_y,  # 起始点坐标
+                            'end_point_x': center_1.get('center')[0],  # 入弯坐标
+                            'end_point_y': center_1.get('center')[1] + radius,  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_1.get('center')[0],  # 入弯坐标
+                            "start_point_y": center_1.get('center')[1] + radius,  # 入弯坐标
+                            "center_point_x": center_1.get('center')[0],  # 圆心坐标
+                            "center_point_y": center_1.get('center')[1],  # 圆心坐标
+                            "end_point_x": center_1.get('center')[0] - radius,  # 出弯坐标
+                            "end_point_y": center_1.get('center')[1],  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_1.get('center')[0] - radius,  # 出弯坐标
+                            'start_point_y': center_1.get('center')[1],  # 出弯坐标
+                            'end_point_x': center_2.get('center')[0] - radius,  # 入弯坐标
+                            'end_point_y': center_2.get('center')[1],  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_2.get('center')[0] - radius,  # 入弯坐标
+                            "start_point_y": center_2.get('center')[1],  # 入弯坐标
+                            "center_point_x": center_2.get('center')[0],  # 圆心坐标
+                            "center_point_y": center_2.get('center')[1],  # 圆心坐标
+                            "end_point_x": center_2.get('center')[0],  # 出弯坐标
+                            "end_point_y": center_2.get('center')[1] - radius,  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_2.get('center')[0],  # 出弯坐标
+                            'start_point_y': center_2.get('center')[1] - radius,  # 出弯坐标
+                            'end_point_x': center_2.get('center')[0] + 10,  # 3点方向移动10米,摆正车身,为倒车准备
+                            'end_point_y': center_2.get('center')[1] - radius,  # 3点方向移动10米,摆正车身,为倒车准备
+                        },
+                    ]
+                },
+                {
+                    'type': 'backward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': center_2.get('center')[0] + 10,  # 3点方向移动10米,摆正车身,为倒车准备
+                            'start_point_y': center_2.get('center')[1] - radius,  # 3点方向移动10米,摆正车身,为倒车准备
+                            'end_point_x': e_point_item.get('point')[0],  # 倒渣口坐标
+                            'end_point_y': e_point_item.get('point')[1],  # 倒渣口坐标
+                        },
+                    ]
+                }
+            ]
+
+
+if __name__ == '__main__':
+    # --- test ---
+    # 情况1:判断是否是在正前方的情况1 M.10 -> M.1
+    # out = test(current_direction_type=9, start_x=74, start_y=205.75, target_point_name='M.1')
+    # 情况2:判断是否是在正前方的情况2 M.12 -> M.20
+    # out = test(current_direction_type=3, start_x=85, start_y=205.75, target_point_name='M.20')
+    # 情况3:车头9方向,向6点行驶,并拐弯情况(共2个弯,1个是出弯,1个是入弯)
+    # out = test(current_direction_type=9, start_x=85, start_y=205.75, target_point_name='F.20')
+    # 情况4:渣罐位,车头9点方向,6点拐弯,3点拐弯,倒车倒入指定倒渣口
+    out = test(current_direction_type=9, start_x=74, start_y=205.75, target_point_name='dump.CD')
+    print(out)

+ 604 - 0
project-fastapi-hs/unit/Scheduler_c1.py

@@ -0,0 +1,604 @@
+"""
+tips:
+    推算半径:13.7
+    倒车与正开的偏移量是5米;倒车中心点=正开中心点+5
+"""
+
+# 坐标数据文件
+data_file_dir = '../data'
+data_file_path = '../data/渣罐坐标数据.json'
+# data_file_path = '/home/ubuntu/repositories/repositories/casperz.py-project/project-fastapi-hs/data/渣罐坐标数据.json'
+# data_file_path = r"E:\casper\repositories\repositories\casperz.py-project\project-fastapi-hs\data\渣罐坐标数据.json"
+
+# 道路名称列表
+path_name_list = [
+    'MN',
+    'KL',
+    'IJ',
+    'GH',
+    'EF',
+    'CD',
+    'AB',
+]
+
+# 渣罐坐标字典
+d4 = {
+
+    # 停车区坐标
+    'pack.1': {'point': (11, 22)},
+    'pack.2': {'point': (11, 22)},
+
+    # 接渣口坐标
+    'load.1': {'point': (11, 22)},
+    'load.2': {'point': (11, 22)},
+    'load.3': {'point': (11, 22)},
+
+    # 倒渣口坐标
+    'dump.MN': {'point': (11, 205.75)},  # 1号倒渣口
+    'dump.KL': {'point': (11, 177.75)},  # 2号倒渣口
+    'dump.IJ': {'point': (11, 149.75)},  # 3号倒渣口
+    'dump.GH': {'point': (11, 121.75)},  # 4号倒渣口
+    'dump.EF': {'point': (11, 93.75)},  # 5号倒渣口
+    'dump.CD': {'point': (11, 65.75)},  # 6号倒渣口
+    'dump.AB': {'point': (11, 37.75)},  # 7号倒渣口
+
+    # 渣罐位坐标
+    'M.31': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.30': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.29': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.28': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.27': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.26': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.25': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.24': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.23': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.22': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.21': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.20': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.19': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.18': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.17': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.16': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.15': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.14': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.13': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.12': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.11': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.10': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.9': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.8': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.7': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.6': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.5': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.4': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.3': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.2': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.1': {'pot': (22, 33), 'road_center': (44, 55)},
+
+    'F.31': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.30': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.29': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.28': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.27': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.26': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.25': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.24': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.23': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.22': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.21': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.20': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.19': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.18': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.17': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.16': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.15': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.14': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.13': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.12': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.11': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.10': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.9': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.8': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.7': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.6': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.5': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.4': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.3': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.2': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.1': {'pot': (22, 33), 'road_center': (44, 55)},
+
+}
+
+
+def get_nearest_point_name_from_pot_point(current_x, current_y):
+    """
+    获取当前位置距离最忌的罐体位置名称
+    """
+    current_x = float(current_x)
+    current_y = float(current_y)
+    min_distance = float('inf')  # 初始值无穷大
+    nearest_point_name = str()  # 最近点名称
+
+    # --- check road center ---
+    for k, v in d4.items():
+        if 'pack' in k or 'load' in k or 'dump' in k:
+            continue
+        point_x = float(v.get('road_center')[0])
+        point_y = float(v.get('road_center')[1])
+        distance = ((point_x - current_x) ** 2 + (point_y - current_y) ** 2) ** (1 / 2)
+        if distance < min_distance:
+            min_distance = distance
+            nearest_point_name = k
+
+    # --- check dump port ---
+    for k, v in d4.items():
+        if 'dump' not in k:
+            continue
+        point_x = float(v.get('point')[0])
+        point_y = float(v.get('point')[1])
+        distance = ((point_x - current_x) ** 2 + (point_y - current_y) ** 2) ** (1 / 2)
+        if distance < min_distance:
+            min_distance = distance
+            nearest_point_name = k
+
+    return nearest_point_name
+
+
+def get_nearest_point_from_road_center_point(x, y):
+    """
+    获取当前所在位置最接近的坐标位置
+    """
+    x = float(x)
+    y = float(y)
+    min_distance = float('inf')  # 初始值无穷大
+    for k, v in d4.items():
+        center_x = float(v.get('road_center')[0])
+        center_y = float(v.get('road_center')[1])
+        distance = ((center_x - x) ** 2 + (center_y - y) ** 2) ** (1 / 2)
+
+        if distance < min_distance:
+            min_distance = distance
+            nearest_point = center_x, center_y
+
+    return nearest_point
+
+
+def get_corner_point(w1='N', w2='head'):
+    """获取拐弯点"""
+    if w2 == 'head':
+        return d4.get(f"{w1}.{1}").get('road_center')
+    elif w2 == 'tail':
+        return d4.get(f"{w1}.{31}").get('road_center')
+
+
+def get_path_name_by_pot_name(pot_name='M.25'):
+    """根据渣罐名获取道路名"""
+    s1, s2 = pot_name.split('.')
+    for s in path_name_list:
+        if s1 in s:
+            return s
+
+
+def get_path_name_by_dump_name(dump_name='dump.MN'):
+    """根据渣罐名获取道路名"""
+    _, s2 = dump_name.split('.')
+    return s2
+
+
+import json
+
+
+def test(current_direction_type=3, start_x=11, start_y=22, target_point_name='M.1'):
+    """
+    current_direction_type: 起始方向  3 围栏方向 9 渣场方向 12 维修间方向 6 维修间正对方向 0 未知(默认值)
+    s_name: 起始位置
+    e_name: 目标位置
+    """
+
+    # --- update d4 ---
+    data = json.load(open(data_file_path, 'rb'))
+    for item in data.get('pot_name|pot_x|pot_y|road_center_x|road_center_y'):
+        pot_name, pot_x, pot_y, road_center_x, road_center_y = item.split('|')
+        d4[pot_name] = {
+            'pot': (float(pot_x), float(pot_y)),
+            'road_center': (float(road_center_x), float(road_center_y)),
+        }
+
+    # --- update d4 ---
+    data = json.load(open(f"{data_file_dir}/倒渣口坐标数据.json", 'rb'))
+    for item in data.get('name|x|y'):
+        name, x, y = item.split('|')
+        d4[name] = {
+            'point': (float(x), float(y)),
+        }
+
+    # --- check ---
+    if not start_x and not start_y:
+        return "Reason: 参数缺失"
+
+    # --- check ---
+    if current_direction_type not in [3, 9]:
+        return "Reason: 暂不支持"
+
+    # --- define ---
+    current_point_name = get_nearest_point_name_from_pot_point(start_x, start_y)
+    s_point_item = dict(**d4.get(current_point_name), **{'name': current_point_name})
+    e_point_item = dict(**d4.get(target_point_name), **{'name': target_point_name})
+
+    # --- define ---
+    radius = 14  # 转弯半径
+    s_s1, s_s2 = current_point_name.split('.')
+    e_s1, e_s2 = target_point_name.split('.')
+    print(f"current_point_name: {current_point_name}")
+    print(f"target_point_name: {target_point_name}")
+
+    # --- check ---
+    if 'dump' not in current_point_name and 'dump' not in target_point_name:
+        if current_direction_type == 9 and s_s1 == e_s1 and int(s_s2) > int(e_s2):
+            """
+            情况1:判断是否是在正前方的情况1
+            """
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        {
+                            # 直行轨迹坐标参数
+                            'start_point_x': s_point_item.get('road_center')[0],
+                            'start_point_y': s_point_item.get('road_center')[1],
+                            'end_point_x': e_point_item.get('road_center')[0],
+                            'end_point_y': e_point_item.get('road_center')[1],
+                        },
+                    ],
+                }
+            ]
+        elif current_direction_type == 3 and s_s1 == e_s1 and int(s_s2) < int(e_s2):
+            """
+            情况2:判断是否是在正前方的情况2
+            """
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('road_center')[0],
+                            'start_point_y': s_point_item.get('road_center')[1],
+                            'end_point_x': e_point_item.get('road_center')[0],
+                            'end_point_y': e_point_item.get('road_center')[1],
+                        },
+                    ],
+                }
+            ]
+        elif current_direction_type == 9 and s_s1 > e_s1:
+            """
+            情况3:车头9方向,向9点6点3点行驶(共2个弯,1个是出弯,1个是入弯)
+            """
+            path_name = get_path_name_by_pot_name(current_point_name)
+            corner_name = path_name[:1]  # 拐弯点(9点到6点方向转弯)(取首位)
+            corner_point_1 = get_corner_point(w1=corner_name, w2='head')
+            corner_point_1_x = corner_point_1[0]
+            corner_point_1_y = corner_point_1[1]
+            path_name = get_path_name_by_pot_name(target_point_name)
+            corner_name = path_name[-1:]  # 拐弯点(6点到3点方向转弯)(取末位)
+            corner_point_2 = get_corner_point(w1=corner_name, w2='head')
+            corner_point_2_x = corner_point_2[0]
+            corner_point_2_y = corner_point_2[1]
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('road_center')[0],  # 起始点坐标
+                            'start_point_y': s_point_item.get('road_center')[1],  # 起始点坐标
+                            'end_point_x': corner_point_1_x,  # 入弯坐标
+                            'end_point_y': corner_point_1_y,  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": corner_point_1_x,  # 入弯坐标
+                            "start_point_y": corner_point_1_y,  # 入弯坐标
+                            "center_point_x": corner_point_1_x,  # 圆心坐标
+                            "center_point_y": corner_point_1_y - radius,  # 圆心坐标
+                            "end_point_x": corner_point_1_x - radius,  # 出弯坐标
+                            "end_point_y": corner_point_1_y - radius,  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': corner_point_1_x - radius,  # 出弯坐标
+                            'start_point_y': corner_point_1_y - radius,  # 出弯坐标
+                            'end_point_x': corner_point_2_x - radius,  # 入弯坐标
+                            'end_point_y': corner_point_2_y + radius,  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": corner_point_2_x - radius,  # 入弯坐标
+                            "start_point_y": corner_point_2_y + radius,  # 入弯坐标
+                            "center_point_x": corner_point_2_x,  # 圆心坐标
+                            "center_point_y": corner_point_2_y + radius,  # 圆心坐标
+                            "end_point_x": corner_point_2_x,  # 出弯坐标
+                            "end_point_y": corner_point_2_y,  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': corner_point_2_x,  # 出弯坐标
+                            'start_point_y': corner_point_2_y,  # 出弯坐标
+                            'end_point_x': e_point_item.get('road_center')[0],  # 目标点坐标
+                            'end_point_y': e_point_item.get('road_center')[1],  # 目标点坐标
+                        },
+                    ],
+                }
+            ]
+        elif current_direction_type == 9 and s_s1 == e_s1 and int(s_s2) < int(e_s2):
+            """
+            情况5:判断是否是在正后方的情况1 M.3 -> M.10(共2个弯)
+            """
+            path_name = get_path_name_by_pot_name(current_point_name)
+            corner_name = path_name[:1]  # 拐弯点(9点到6点方向转弯)(取首位)
+            corner_point_1 = get_corner_point(w1=corner_name, w2='head')
+            corner_point_1_x = corner_point_1[0]
+            corner_point_1_y = corner_point_1[1]
+            corner_point_2 = get_corner_point(w1=corner_name, w2='tail')
+            corner_point_2_x = corner_point_2[0]
+            corner_point_2_y = corner_point_2[0]
+            # print(f"corner_point_1: {corner_point_1}")
+            # print(f"corner_point_2: {corner_point_2}")
+            # return "Result: 正在开发"
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('road_center')[0],  # 起始点坐标
+                            'start_point_y': s_point_item.get('road_center')[1],  # 起始点坐标
+                            'end_point_x': corner_point_1_x,  # 入弯坐标
+                            'end_point_y': corner_point_1_y,  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": corner_point_1_x,  # 入弯坐标
+                            "start_point_y": corner_point_1_y,  # 入弯坐标
+                            "center_point_x": corner_point_1_x,  # 圆心坐标
+                            "center_point_y": corner_point_1_y - radius,  # 圆心坐标
+                            "end_point_x": corner_point_1_x,  # 出弯坐标
+                            "end_point_y": corner_point_1_y - radius - radius,  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': corner_point_1_x,  # 出弯坐标
+                            'start_point_y': corner_point_1_y - radius - radius,  # 出弯坐标
+                            'end_point_x': corner_point_2_x,  # 入弯坐标
+                            'end_point_y': corner_point_2_y - radius - radius,  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": corner_point_2_x,  # 入弯坐标
+                            "start_point_y": corner_point_2_y - radius - radius,  # 入弯坐标
+                            "center_point_x": corner_point_2_x,  # 圆心坐标
+                            "center_point_y": corner_point_2_y - radius,  # 圆心坐标
+                            "end_point_x": corner_point_2_x,  # 出弯坐标
+                            "end_point_y": corner_point_2_y,  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': corner_point_2_x,  # 出弯坐标
+                            'start_point_y': corner_point_2_y,  # 出弯坐标
+                            'end_point_x': e_point_item.get('road_center')[0],  # 目标点坐标
+                            'end_point_y': e_point_item.get('road_center')[1],  # 目标点坐标
+                        },
+                    ]
+                }
+            ]
+        else:
+            return "Result: 暂不支持"
+
+    elif 'dump' not in current_point_name and 'dump' in target_point_name:
+        if current_direction_type == 9:
+            """
+            情况4:渣罐位,车头9点方向,6点拐弯,3点拐弯,倒车倒入指定倒渣口
+            """
+            path_name = get_path_name_by_pot_name(current_point_name)
+            # print(f"path_name: {path_name}")
+            corner_name = path_name[:1]  # 拐弯点(9点到6点方向转弯)(取首位)
+            corner_point_1 = get_corner_point(w1=corner_name, w2='head')
+            corner_point_1_x = corner_point_1[0]
+            corner_point_1_y = corner_point_1[1]
+            # print(f"corner_point_1: {corner_point_1}")
+            path_name = get_path_name_by_dump_name(target_point_name)
+            corner_name = path_name[-1:]  # 拐弯点(6点到3点方向转弯)(取末位)
+            corner_point_2 = get_corner_point(w1=corner_name, w2='head')
+            corner_point_2_x = corner_point_2[0]
+            corner_point_2_y = corner_point_2[1]
+            # print(f"corner_point_2: {corner_point_2}")
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('road_center')[0],  # 起始点坐标
+                            'start_point_y': s_point_item.get('road_center')[1],  # 起始点坐标
+                            'end_point_x': corner_point_1_x,  # 入弯坐标
+                            'end_point_y': corner_point_1_y,  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": corner_point_1_x,  # 入弯坐标
+                            "start_point_y": corner_point_1_y,  # 入弯坐标
+                            "center_point_x": corner_point_1_x,  # 圆心坐标
+                            "center_point_y": corner_point_1_y - radius,  # 圆心坐标
+                            "end_point_x": corner_point_1_x - radius,  # 出弯坐标
+                            "end_point_y": corner_point_1_y - radius,  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': corner_point_1_x - radius,  # 出弯坐标
+                            'start_point_y': corner_point_1_y - radius,  # 出弯坐标
+                            'end_point_x': corner_point_2_x - radius,  # 入弯坐标
+                            'end_point_y': corner_point_2_y + radius,  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": corner_point_2_x - radius,  # 入弯坐标
+                            "start_point_y": corner_point_2_y + radius,  # 入弯坐标
+                            "center_point_x": corner_point_2_x,  # 圆心坐标
+                            "center_point_y": corner_point_2_y + radius,  # 圆心坐标
+                            "end_point_x": corner_point_2_x,  # 出弯坐标
+                            "end_point_y": corner_point_2_y,  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': corner_point_2_x,  # 出弯坐标
+                            'start_point_y': corner_point_2_y,  # 出弯坐标
+                            'end_point_x': corner_point_2_x + 30,  # 前进30米,摆正车身,为倒车准备
+                            'end_point_y': corner_point_2_y,  # 摆正车身,为倒车准备
+                        },
+                    ]
+                },
+                {
+                    'type': 'backward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': corner_point_2_x + 30,  # 前进30米,摆正车身,为倒车准备
+                            'start_point_y': corner_point_2_y,  # 摆正车身,为倒车准备
+                            'end_point_x': e_point_item.get('point')[0],  # 倒渣口坐标
+                            'end_point_y': e_point_item.get('point')[1],  # 倒渣口坐标
+                        },
+                    ]
+                }
+            ]
+        else:
+            return "Result: 暂不支持"
+    elif 'dump' in current_point_name and 'dump' not in target_point_name and current_direction_type == 3:
+        if e_s1 in s_s2:
+            """
+            情况6:倒渣位,车头3点方向,目标点正对目标巷道情况
+            """
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('point')[0],  # 起始点坐标
+                            'start_point_y': s_point_item.get('point')[1],  # 起始点坐标
+                            'end_point_x': e_point_item.get('road_center')[0],  # 目标点坐标
+                            'end_point_y': e_point_item.get('road_center')[1],  # 目标点坐标
+                        },
+                    ]
+                }
+            ]
+        elif e_s1 > s_s2[1]:
+            """
+            情况7:倒渣位,车头3点方向,目标点位于巷道上侧
+            """
+            path_name = get_path_name_by_dump_name(current_point_name)
+            corner_name = path_name[-1:]  # 拐弯点(3点->12点)(取末位)
+            corner_point_1_x, corner_point_1_y = get_corner_point(w1=corner_name, w2='tail')
+            path_name = get_path_name_by_pot_name(target_point_name)
+            corner_name = path_name[:1]  # 拐弯点(12点->9点)(取首位)
+            corner_point_2_x, corner_point_2_y = get_corner_point(w1=corner_name, w2='tail')
+            # print(f"corner_point_2_x: {corner_point_2_x}")
+            # print(f"corner_point_2_y: {corner_point_2_y}")
+            nav_coordinates_1 = [
+                # 直线行驶
+                {
+                    'start_point_x': s_point_item.get('point')[0],  # 起始点坐标
+                    'start_point_y': s_point_item.get('point')[1],  # 起始点坐标
+                    'end_point_x': corner_point_1_x,  # 入弯坐标
+                    'end_point_y': corner_point_1_y,  # 入弯坐标
+                },
+                # 转弯行驶
+                {
+                    "start_point_x": corner_point_1_x,  # 入弯坐标
+                    "start_point_y": corner_point_1_y,  # 入弯坐标
+                    "center_point_x": corner_point_1_x,  # 圆心坐标
+                    "center_point_y": corner_point_1_y + radius,  # 圆心坐标
+                    "end_point_x": corner_point_1_x + radius,  # 出弯坐标
+                    "end_point_y": corner_point_1_y + radius,  # 出弯坐标
+                },
+
+            ]
+            nav_coordinates_2 = [
+                # 直线行驶
+                {
+                    'start_point_x': corner_point_1_x + radius,  # 出弯坐标
+                    'start_point_y': corner_point_1_y + radius,  # 出弯坐标
+                    'end_point_x': corner_point_2_x + radius,  # 入弯坐标
+                    'end_point_y': corner_point_2_y - radius,  # 入弯坐标
+                }
+            ]
+            # --- check ---
+            if (corner_point_1_x + radius == corner_point_2_x + radius
+                    or corner_point_1_y + radius == corner_point_2_y - radius):
+                nav_coordinates_2 = []
+            nav_coordinates_3 = [
+                # 转弯行驶
+                {
+                    "start_point_x": corner_point_2_x + radius,  # 入弯坐标
+                    "start_point_y": corner_point_2_y - radius,  # 入弯坐标
+                    "center_point_x": corner_point_2_x,  # 圆心坐标
+                    "center_point_y": corner_point_2_y - radius,  # 圆心坐标
+                    "end_point_x": corner_point_2_x,  # 出弯坐标
+                    "end_point_y": corner_point_2_y,  # 出弯坐标
+                },
+                # 直线行驶
+                {
+                    'start_point_x': corner_point_2_x,  # 出弯坐标
+                    'start_point_y': corner_point_2_y,  # 出弯坐标
+                    'end_point_x': e_point_item.get('road_center')[0],  # 目标点坐标
+                    'end_point_y': e_point_item.get('road_center')[1],  # 目标点坐标
+                }
+            ]
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': nav_coordinates_1 + nav_coordinates_2 + nav_coordinates_3
+                }
+            ]
+        elif e_s1 < s_s2[0]:
+            """
+            情况8:倒渣位,车头3点方向,目标点位于巷道下侧
+            """
+            return "Result: 暂不支持 222"
+        else:
+            return "Result: 暂不支持"
+        return "Result: 暂不支持"
+    else:
+        return "Result: 暂不支持"
+
+
+if __name__ == '__main__':
+    # --- 现场实际测试数据 ---
+    offset = 6.4
+    M01 = 14.450 + (offset * 0), 199.64
+    M31 = 184.93 - (offset * 6), 195.7
+    M03 = 35.5, 205.75
+    M12 = 85, 205.75
+    # --- cad理论测试数据 ---
+    dumpCD = 4.18, 65.75
+    # 情况1:判断是否是在正前方的情况1 M.10 -> M.2
+    # out = test(current_direction_type=9, start_x=M31[0], start_y=M31[1], target_point_name='M.1')
+    # 情况2:判断是否是在正前方的情况2 M.12 -> M.20
+    # out = test(current_direction_type=3, start_x=M12[0], start_y=M12[1], target_point_name='M.20')
+    # 情况3:车头9方向,向6点行驶,并拐弯情况(共2个弯,1个是出弯,1个是入弯)
+    # out = test(current_direction_type=9, start_x=M12[0], start_y=M12[1], target_point_name='F.20')
+    # 情况4:渣罐位,车头9点方向,6点拐弯,3点拐弯,倒车倒入指定倒渣口
+    # out = test(current_direction_type=9, start_x=M31[0], start_y=M31[1], target_point_name='dump.CD')
+    # out = test(current_direction_type=9, start_x=M03[0], start_y=M03[1], target_point_name='dump.CD')
+    # 情况5:判断是否是在正后方的情况1 M.3 -> M.10(共2个弯)
+    # out = test(current_direction_type=9, start_x=M01[0], start_y=M01[1], target_point_name='M.10')
+    # out = test(current_direction_type=9, start_x=M03[0], start_y=M03[1], target_point_name='M.10')
+    # 情况6:倒渣位,车头3点方向,目标点正对目标巷道情况
+    # out = test(current_direction_type=3, start_x=dumpCD[0], start_y=dumpCD[1], target_point_name='D.20')
+    # 情况7:倒渣位,车头3点方向,目标点位于巷道上侧
+    # out = test(current_direction_type=3, start_x=dumpCD[0], start_y=dumpCD[1], target_point_name='M.1')
+    # 情况8:倒渣位,车头3点方向,目标点位于巷道下侧  todo 存在一上来就转弯,有个圆心怎么算呢
+    # out = test(current_direction_type=3, start_x=dumpCD[0], start_y=dumpCD[1], target_point_name='A.30')
+    print(out)
+    # --- test ---
+    # out = get_nearest_point_name_from_pot_point(dumpCD[0], dumpCD[1])
+    # print(out)

+ 648 - 0
project-fastapi-hs/unit/Scheduler_d1.py

@@ -0,0 +1,648 @@
+"""
+tips:
+    推算半径:13.7
+    倒车与正开的偏移量是5米;倒车中心点=正开中心点+5
+"""
+
+# 坐标数据文件
+data_file_dir = '../data'
+data_file_path = '../data/渣罐坐标数据.json'
+# data_file_path = '/home/ubuntu/repositories/repositories/casperz.py-project/project-fastapi-hs/data/渣罐坐标数据.json'
+# data_file_path = r"E:\casper\repositories\repositories\casperz.py-project\project-fastapi-hs\data\渣罐坐标数据.json"
+
+# 道路名称列表
+path_name_list = [
+    'MN',
+    'KL',
+    'IJ',
+    'GH',
+    'EF',
+    'CD',
+    'AB',
+]
+
+# 圆心点坐标字典
+d1 = {
+    'LM.head': {'center_x': 11, 'center_y': 22},
+    'JK.head': {'center_x': 11, 'center_y': 22},
+    'HI.head': {'center_x': 11, 'center_y': 22},
+    'FG.head': {'center_x': 11, 'center_y': 22},
+    'DE.head': {'center_x': 11, 'center_y': 22},
+    'BC.head': {'center_x': 11, 'center_y': 22},
+    'A.head': {'center_x': 11, 'center_y': 22},
+
+    'LM.tail': {'center_x': 11, 'center_y': 22},
+    'JK.tail': {'center_x': 11, 'center_y': 22},
+    'HI.tail': {'center_x': 11, 'center_y': 22},
+    'FG.tail': {'center_x': 11, 'center_y': 22},
+    'DE.tail': {'center_x': 11, 'center_y': 22},
+    'BC.tail': {'center_x': 11, 'center_y': 22},
+    'A.tail': {'center_x': 11, 'center_y': 22},
+
+}
+
+# 渣罐坐标字典
+d4 = {
+
+    # 停车区坐标
+    'pack.1': {'point': (11, 22)},
+    'pack.2': {'point': (11, 22)},
+
+    # 接渣口坐标
+    'load.1': {'point': (11, 22)},
+    'load.2': {'point': (11, 22)},
+    'load.3': {'point': (11, 22)},
+
+    # 倒渣口坐标
+    'dump.MN': {'point': (11, 205.75)},  # 1号倒渣口
+    'dump.KL': {'point': (11, 177.75)},  # 2号倒渣口
+    'dump.IJ': {'point': (11, 149.75)},  # 3号倒渣口
+    'dump.GH': {'point': (11, 121.75)},  # 4号倒渣口
+    'dump.EF': {'point': (11, 93.75)},  # 5号倒渣口
+    'dump.CD': {'point': (11, 65.75)},  # 6号倒渣口
+    'dump.AB': {'point': (11, 37.75)},  # 7号倒渣口
+
+    # 渣罐位坐标
+    'M.31': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.30': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.29': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.28': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.27': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.26': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.25': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.24': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.23': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.22': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.21': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.20': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.19': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.18': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.17': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.16': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.15': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.14': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.13': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.12': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.11': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.10': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.9': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.8': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.7': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.6': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.5': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.4': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.3': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.2': {'pot': (22, 33), 'road_center': (44, 55)},
+    'M.1': {'pot': (22, 33), 'road_center': (44, 55)},
+
+    'F.31': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.30': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.29': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.28': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.27': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.26': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.25': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.24': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.23': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.22': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.21': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.20': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.19': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.18': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.17': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.16': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.15': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.14': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.13': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.12': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.11': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.10': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.9': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.8': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.7': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.6': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.5': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.4': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.3': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.2': {'pot': (22, 33), 'road_center': (44, 55)},
+    'F.1': {'pot': (22, 33), 'road_center': (44, 55)},
+
+}
+
+
+def get_nearest_point_name_from_pot_point(current_x, current_y):
+    """
+    获取当前位置距离最忌的罐体位置名称
+    """
+    current_x = float(current_x)
+    current_y = float(current_y)
+    min_distance = float('inf')  # 初始值无穷大
+    nearest_point_name = str()  # 最近点名称
+
+    # --- check road center ---
+    for k, v in d4.items():
+        if 'pack' in k or 'load' in k or 'dump' in k:
+            continue
+        point_x = float(v.get('road_center')[0])
+        point_y = float(v.get('road_center')[1])
+        distance = ((point_x - current_x) ** 2 + (point_y - current_y) ** 2) ** (1 / 2)
+        if distance < min_distance:
+            min_distance = distance
+            nearest_point_name = k
+
+    # --- check dump port ---
+    for k, v in d4.items():
+        if 'dump' not in k:
+            continue
+        point_x = float(v.get('point')[0])
+        point_y = float(v.get('point')[1])
+        distance = ((point_x - current_x) ** 2 + (point_y - current_y) ** 2) ** (1 / 2)
+        if distance < min_distance:
+            min_distance = distance
+            nearest_point_name = k
+
+    return nearest_point_name
+
+
+def get_nearest_point_from_road_center_point(x, y):
+    """
+    获取当前所在位置最接近的坐标位置
+    """
+    x = float(x)
+    y = float(y)
+    min_distance = float('inf')  # 初始值无穷大
+    for k, v in d4.items():
+        center_x = float(v.get('road_center')[0])
+        center_y = float(v.get('road_center')[1])
+        distance = ((center_x - x) ** 2 + (center_y - y) ** 2) ** (1 / 2)
+
+        if distance < min_distance:
+            min_distance = distance
+            nearest_point = center_x, center_y
+
+    return nearest_point
+
+
+def get_path_name_by_pot_name(pot_name='M.25'):
+    """根据渣罐名获取道路名"""
+    s1, s2 = pot_name.split('.')
+    for s in path_name_list:
+        if s1 in s:
+            return s
+
+
+def get_path_name_by_dump_name(dump_name='dump.MN'):
+    """根据渣罐名获取道路名"""
+    _, s2 = dump_name.split('.')
+    return s2
+
+
+def get_center_point(w1='N', w2='head'):
+    """
+    获取圆心点坐标
+    w1: 关键字
+    w2: 关键字
+    """
+    for k, v in d1.items():
+        k_s1, k_s2 = k.split('.')
+        if w1 not in k_s1:
+            continue
+        if w2 not in k_s2:
+            continue
+        return dict(**v, **{'name': k})
+
+
+import json
+import numpy
+
+
+def test(current_direction_type=3, start_x=11, start_y=22, target_point_name='M.1'):
+    """
+    current_direction_type: 起始方向  3 围栏方向 9 渣场方向 12 维修间方向 6 维修间正对方向 0 未知(默认值)
+    start_x: 起始位置
+    start_y: 起始位置
+    target_point_name: 目标名称
+    """
+
+    # --- update d1 ---
+    data = json.load(open(f"{data_file_dir}/圆心坐标数据.json", 'rb'))
+    for item in data.get(
+            'name|center_x|center_y|center_3_x|center_3_y|center_9_x|center_9_y|center_12_x|center_12_y|center_6_x|center_6_y'):
+        name, center_x, center_y, center_3_x, center_3_y, center_9_x, center_9_y, center_12_x, center_12_y, center_6_x, center_6_y = item.split(
+            '|')
+        d1[name] = {
+            'center_x': float(center_x),
+            'center_y': float(center_y),
+            'center_3_x': float(center_3_x),
+            'center_3_y': float(center_3_y),
+            'center_9_x': float(center_9_x),
+            'center_9_y': float(center_9_y),
+            'center_12_x': float(center_12_x),
+            'center_12_y': float(center_12_y),
+            'center_6_x': float(center_6_x),
+            'center_6_y': float(center_6_y),
+        }
+
+    # --- update d4 ---
+    data = json.load(open(data_file_path, 'rb'))
+    for item in data.get('pot_name|pot_x|pot_y|road_center_x|road_center_y'):
+        pot_name, pot_x, pot_y, road_center_x, road_center_y = item.split('|')
+        d4[pot_name] = {
+            'pot': (float(pot_x), float(pot_y)),
+            'road_center': (float(road_center_x), float(road_center_y)),
+        }
+
+    # --- update d4 ---
+    data = json.load(open(f"{data_file_dir}/倒渣口坐标数据.json", 'rb'))
+    for item in data.get('name|x|y'):
+        name, x, y = item.split('|')
+        d4[name] = {
+            'point': (float(x), float(y)),
+        }
+
+    # --- check ---
+    if not start_x and not start_y:
+        return "Reason: 参数缺失"
+
+    # --- check ---
+    if current_direction_type not in [3, 9]:
+        return "Reason: 暂不支持"
+
+    # --- define ---
+    current_point_name = get_nearest_point_name_from_pot_point(start_x, start_y)
+    s_point_item = dict(**d4.get(current_point_name), **{'name': current_point_name})
+    e_point_item = dict(**d4.get(target_point_name), **{'name': target_point_name})
+
+    # --- define ---
+    radius = 14  # 转弯半径
+    s_s1, s_s2 = current_point_name.split('.')
+    e_s1, e_s2 = target_point_name.split('.')
+    print(f"current_point_name: {current_point_name}")
+    print(f"target_point_name: {target_point_name}")
+
+    # --- check ---
+    if 'dump' not in current_point_name and 'dump' not in target_point_name:
+        if current_direction_type == 9 and s_s1 == e_s1 and int(s_s2) > int(e_s2):
+            """
+            情况1:判断是否是在正前方的情况1
+            """
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        {
+                            # 直行轨迹坐标参数
+                            'start_point_x': s_point_item.get('road_center')[0],
+                            'start_point_y': s_point_item.get('road_center')[1],
+                            'end_point_x': e_point_item.get('road_center')[0],
+                            'end_point_y': e_point_item.get('road_center')[1],
+                        },
+                    ],
+                }
+            ]
+        elif current_direction_type == 3 and s_s1 == e_s1 and int(s_s2) < int(e_s2):
+            """
+            情况2:判断是否是在正前方的情况2
+            """
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('road_center')[0],
+                            'start_point_y': s_point_item.get('road_center')[1],
+                            'end_point_x': e_point_item.get('road_center')[0],
+                            'end_point_y': e_point_item.get('road_center')[1],
+                        },
+                    ],
+                }
+            ]
+        elif current_direction_type == 9 and s_s1 > e_s1:
+            """
+            情况3:从起始渣罐位向9点6点3点方向行驶到目标渣罐位
+            """
+            path_name = get_path_name_by_pot_name(current_point_name)
+            corner_name = path_name[:1]  # 拐弯点 9点向6点 取首位
+            center_point_1 = get_center_point(w1=corner_name, w2='head')
+            path_name = get_path_name_by_pot_name(target_point_name)
+            corner_name = path_name[-1:]  # 拐弯点 6点向3点 取末位
+            center_point_2 = get_center_point(w1=corner_name, w2='head')
+            # print(f"debug.294: center_point_1: {center_point_1}")
+            # print(f"debug.294: center_point_2: {center_point_2}")
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('road_center')[0],  # 起始点坐标
+                            'start_point_y': s_point_item.get('road_center')[1],  # 起始点坐标
+                            'end_point_x': center_point_1.get('center_12_x'),  # 入弯坐标
+                            'end_point_y': center_point_1.get('center_12_y'),  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_point_1.get('center_12_x'),  # 入弯坐标
+                            "start_point_y": center_point_1.get('center_12_y'),  # 入弯坐标
+                            "center_point_x": center_point_1.get('center_x'),  # 圆心坐标
+                            "center_point_y": center_point_1.get('center_y'),  # 圆心坐标
+                            "end_point_x": center_point_1.get('center_9_x'),  # 出弯坐标
+                            "end_point_y": center_point_1.get('center_9_y'),  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_point_1.get('center_9_x'),  # 出弯坐标
+                            'start_point_y': center_point_1.get('center_9_y'),  # 出弯坐标
+                            'end_point_x': center_point_2.get('center_9_x'),  # 入弯坐标
+                            'end_point_y': center_point_2.get('center_9_y'),  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_point_2.get('center_9_x'),  # 入弯坐标
+                            "start_point_y": center_point_2.get('center_9_y'),  # 入弯坐标
+                            "center_point_x": center_point_2.get('center_x'),  # 圆心坐标
+                            "center_point_y": center_point_2.get('center_y'),  # 圆心坐标
+                            "end_point_x": center_point_2.get('center_6_x'),  # 出弯坐标
+                            "end_point_y": center_point_2.get('center_6_y'),  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_point_2.get('center_6_x'),  # 出弯坐标
+                            'start_point_y': center_point_2.get('center_6_y'),  # 出弯坐标
+                            'end_point_x': e_point_item.get('road_center')[0],  # 目标点坐标
+                            'end_point_y': e_point_item.get('road_center')[1],  # 目标点坐标
+                        },
+                    ],
+                }
+            ]
+        elif current_direction_type == 9 and s_s1 == e_s1 and int(s_s2) < int(e_s2):
+            """
+            情况5:判断是否是在正后方的情况
+            """
+            path_name = get_path_name_by_pot_name(current_point_name)
+            corner_name = path_name[:1]  # 拐弯点 9点6点3点 取首位
+            center_point_1 = get_center_point(w1=corner_name, w2='head')
+            center_point_2 = get_center_point(w1=corner_name, w2='tail')
+            print(f"debug.403: center_point_1: {center_point_1}")
+            print(f"debug.403: center_point_2: {center_point_2}")
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('road_center')[0],  # 起始点坐标
+                            'start_point_y': s_point_item.get('road_center')[1],  # 起始点坐标
+                            'end_point_x': center_point_1.get('center_12_x'),  # 入弯坐标
+                            'end_point_y': center_point_1.get('center_12_y'),  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_point_1.get('center_12_x'),  # 入弯坐标
+                            "start_point_y": center_point_1.get('center_12_y'),  # 入弯坐标
+                            "center_point_x": center_point_1.get('center_x'),  # 圆心坐标
+                            "center_point_y": center_point_1.get('center_y'),  # 圆心坐标
+                            "end_point_x": center_point_1.get('center_6_x'),  # 出弯坐标
+                            "end_point_y": center_point_1.get('center_6_y'),  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_point_1.get('center_6_x'),  # 出弯坐标
+                            'start_point_y': center_point_1.get('center_6_x'),  # 出弯坐标
+                            'end_point_x': center_point_2.get('center_6_x'),  # 入弯坐标
+                            'end_point_y': center_point_2.get('center_6_y'),  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_point_2.get('center_6_x'),  # 入弯坐标
+                            "start_point_y": center_point_2.get('center_6_y'),  # 入弯坐标
+                            "center_point_x": center_point_2.get('center_x'),  # 圆心坐标
+                            "center_point_y": center_point_2.get('center_y'),  # 圆心坐标
+                            "end_point_x": center_point_2.get('center_12_x'),  # 出弯坐标
+                            "end_point_y": center_point_2.get('center_12_y'),  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_point_2.get('center_12_x'),  # 出弯坐标
+                            'start_point_y': center_point_2.get('center_12_y'),  # 出弯坐标
+                            'end_point_x': e_point_item.get('road_center')[0],  # 目标点坐标
+                            'end_point_y': e_point_item.get('road_center')[1],  # 目标点坐标
+                        },
+                    ]
+                }
+            ]
+        else:
+            return "Result: 暂不支持"
+
+    elif 'dump' not in current_point_name and 'dump' in target_point_name:
+        if current_direction_type == 9:
+            """
+            情况4:渣罐位,车头9点方向,6点拐弯,3点拐弯,倒车倒入指定倒渣口
+            """
+            # --- get ---
+            path_name = get_path_name_by_pot_name(current_point_name)
+            corner_name = path_name[:1]  # 拐弯点 9点向6点 取首位
+            center_point_1 = get_center_point(w1=corner_name, w2='head')
+            path_name = get_path_name_by_dump_name(target_point_name)
+            corner_name = path_name[-1:]  # 拐弯点 6点向3点 取末位
+            center_point_2 = get_center_point(w1=corner_name, w2='head')
+            print(f"debug.465: center_point_1: {center_point_1}")
+            print(f"debug.465: center_point_2: {center_point_2}")
+            # --- set ---
+            degrees = 1.3
+            short = 30 * numpy.sin(numpy.radians(degrees))
+            long = 30 * numpy.cos(numpy.radians(degrees))
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('road_center')[0],  # 起始点坐标
+                            'start_point_y': s_point_item.get('road_center')[1],  # 起始点坐标
+                            'end_point_x': center_point_1.get('center_12_x'),  # 入弯坐标
+                            'end_point_y': center_point_1.get('center_12_y'),  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_point_1.get('center_12_x'),  # 入弯坐标
+                            "start_point_y": center_point_1.get('center_12_y'),  # 入弯坐标
+                            "center_point_x": center_point_1.get('center_x'),  # 圆心坐标
+                            "center_point_y": center_point_1.get('center_y'),  # 圆心坐标
+                            "end_point_x": center_point_1.get('center_9_x'),  # 出弯坐标
+                            "end_point_y": center_point_1.get('center_9_x'),  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_point_1.get('center_9_x'),  # 出弯坐标
+                            'start_point_y': center_point_1.get('center_9_x'),  # 出弯坐标
+                            'end_point_x': center_point_2.get('center_9_x'),  # 入弯坐标
+                            'end_point_y': center_point_2.get('center_9_y'),  # 入弯坐标
+                        },
+                        # 转弯行驶
+                        {
+                            "start_point_x": center_point_2.get('center_9_x'),  # 入弯坐标
+                            "start_point_y": center_point_2.get('center_9_y'),  # 入弯坐标
+                            "center_point_x": center_point_2.get('center_x'),  # 圆心坐标
+                            "center_point_y": center_point_2.get('center_x'),  # 圆心坐标
+                            "end_point_x": center_point_2.get('center_6_x'),  # 出弯坐标
+                            "end_point_y": center_point_2.get('center_6_y'),  # 出弯坐标
+                        },
+                        # 直线行驶
+                        {
+                            'start_point_x': center_point_2.get('center_6_x'),  # 出弯坐标
+                            'start_point_y': center_point_2.get('center_6_y'),  # 出弯坐标
+                            'end_point_x': center_point_2.get('center_6_x') + long,  # 前进30米,摆正车身,为倒车准备
+                            'end_point_y': center_point_2.get('center_6_y') - short,  # 前进30米,摆正车身,为倒车准备
+                        },
+                    ]
+                },
+                {
+                    'type': 'backward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': center_point_2.get('center_6_x') + long,  # 前进30米,摆正车身,为倒车准备
+                            'start_point_y': center_point_2.get('center_6_y') - short,  # 前进30米,摆正车身,为倒车准备
+                            'end_point_x': e_point_item.get('point')[0],  # 倒渣口坐标
+                            'end_point_y': e_point_item.get('point')[1],  # 倒渣口坐标
+                        },
+                    ]
+                }
+            ]
+        else:
+            return "Result: 暂不支持"
+    elif 'dump' in current_point_name and 'dump' not in target_point_name and current_direction_type == 3:
+        if e_s1 in s_s2:
+            """
+            情况6:倒渣位,车头3点方向,目标点正对目标巷道情况
+            """
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': [
+                        # 直线行驶
+                        {
+                            'start_point_x': s_point_item.get('point')[0],  # 起始点坐标
+                            'start_point_y': s_point_item.get('point')[1],  # 起始点坐标
+                            'end_point_x': e_point_item.get('road_center')[0],  # 目标点坐标
+                            'end_point_y': e_point_item.get('road_center')[1],  # 目标点坐标
+                        },
+                    ]
+                }
+            ]
+        elif e_s1 > s_s2[1]:
+            """
+            情况7:倒渣位,车头3点方向,目标点位于巷道上侧
+            """
+
+            path_name = get_path_name_by_dump_name(current_point_name)
+            corner_name = path_name[-1:]  # 拐弯点 3点12点 取末位
+            center_point_1 = get_center_point(w1=corner_name, w2='tail')
+
+            path_name = get_path_name_by_pot_name(target_point_name)
+            corner_name = path_name[:1]  # 拐弯点 12点9点 取首位
+            center_point_2 = get_center_point(w1=corner_name, w2='tail')
+
+            nav_coordinates_1 = [
+                # 直线行驶
+                {
+                    'start_point_x': s_point_item.get('point')[0],  # 起始点坐标
+                    'start_point_y': s_point_item.get('point')[1],  # 起始点坐标
+                    'end_point_x': center_point_1.get('center_6_x'),  # 入弯坐标
+                    'end_point_y': center_point_1.get('center_6_y'),  # 入弯坐标
+                },
+                # 转弯行驶
+                {
+                    "start_point_x": center_point_1.get('center_6_x'),  # 入弯坐标
+                    "start_point_y": center_point_1.get('center_6_y'),  # 入弯坐标
+                    "center_point_x": center_point_1.get('center_x'),  # 圆心坐标
+                    "center_point_y": center_point_1.get('center_y'),  # 圆心坐标
+                    "end_point_x": center_point_1.get('center_3_x'),  # 出弯坐标
+                    "end_point_y": center_point_1.get('center_3_y'),  # 出弯坐标
+                },
+
+            ]
+            nav_coordinates_2 = [
+                # 直线行驶
+                {
+                    'start_point_x': center_point_1.get('center_3_x'),  # 出弯坐标
+                    'start_point_y': center_point_1.get('center_3_y'),  # 出弯坐标
+                    'end_point_x': center_point_2.get('center_3_x'),  # 入弯坐标
+                    'end_point_y': center_point_2.get('center_3_x'),  # 入弯坐标
+                }
+            ]
+
+            # --- check ---
+            if nav_coordinates_2[0].get('start_point_x') == nav_coordinates_2[0].get('end_point_x'):
+                nav_coordinates_2 = []
+            if nav_coordinates_2[0].get('start_point_y') == nav_coordinates_2[0].get('end_point_y'):
+                nav_coordinates_2 = []
+
+            nav_coordinates_3 = [
+                # 转弯行驶
+                {
+                    "start_point_x": center_point_2.get('center_3_x'),  # 入弯坐标
+                    "start_point_y": center_point_2.get('center_3_x'),  # 入弯坐标
+                    "center_point_x": center_point_2.get('center_x'),  # 圆心坐标
+                    "center_point_y": center_point_2.get('center_y'),  # 圆心坐标
+                    "end_point_x": center_point_2.get('center_12_x'),  # 出弯坐标
+                    "end_point_y": center_point_2.get('center_12_y'),  # 出弯坐标
+                },
+                # 直线行驶
+                {
+                    'start_point_x': center_point_2.get('center_12_x'),  # 出弯坐标
+                    'start_point_y': center_point_2.get('center_12_y'),  # 出弯坐标
+                    'end_point_x': e_point_item.get('road_center')[0],  # 目标点坐标
+                    'end_point_y': e_point_item.get('road_center')[1],  # 目标点坐标
+                }
+            ]
+            return [
+                {
+                    'type': 'forward',
+                    'nav_coordinates': nav_coordinates_1 + nav_coordinates_2 + nav_coordinates_3
+                }
+            ]
+        elif e_s1 < s_s2[0]:
+            """
+            情况8:倒渣位,车头3点方向,目标点位于巷道下侧
+            """
+            return "Result: 暂不支持 222"
+        else:
+            return "Result: 暂不支持"
+        return "Result: 暂不支持"
+    else:
+        return "Result: 暂不支持"
+
+
+if __name__ == '__main__':
+    # --- 现场实际测试数据 ---
+    offset = 6.4
+    M01 = 14.450 + (offset * 0), 199.64
+    M31 = 184.93 - (offset * 6), 195.7
+    M03 = 35.5, 205.75
+    M12 = 85, 205.75
+    dumpCD = 15.49 - 10, 63.06
+    # 情况1:判断是否是在正前方的情况1 M.10 -> M.2
+    # out = test(current_direction_type=9, start_x=M31[0], start_y=M31[1], target_point_name='M.1')
+    # 情况2:判断是否是在正前方的情况2 M.12 -> M.20
+    # out = test(current_direction_type=3, start_x=M12[0], start_y=M12[1], target_point_name='M.20')
+    # 情况3:从起始渣罐位向9点6点3点方向行驶到目标渣罐位 | M.12 -> F.20
+    # out = test(current_direction_type=9, start_x=M12[0], start_y=M12[1], target_point_name='F.20')
+    # 情况4:渣罐位,车头9点方向,6点拐弯,3点拐弯,倒车倒入指定倒渣口
+    # out = test(current_direction_type=9, start_x=M31[0], start_y=M31[1], target_point_name='dump.CD')
+    # out = test(current_direction_type=9, start_x=M03[0], start_y=M03[1], target_point_name='dump.CD')
+    # 情况5:判断是否是在正后方的情况 M.3 -> M.10(共2个弯)
+    # out = test(current_direction_type=9, start_x=M01[0], start_y=M01[1], target_point_name='M.10')
+    # out = test(current_direction_type=9, start_x=M03[0], start_y=M03[1], target_point_name='M.10')
+    # 情况6:倒渣位,车头3点方向,目标点正对目标巷道情况
+    # out = test(current_direction_type=3, start_x=dumpCD[0], start_y=dumpCD[1], target_point_name='D.20')
+    # 情况7:倒渣位,车头3点方向,目标点位于巷道上侧
+    out = test(current_direction_type=3, start_x=dumpCD[0], start_y=dumpCD[1], target_point_name='M.1')
+    # 情况8:倒渣位,车头3点方向,目标点位于巷道下侧  todo 存在一上来就转弯,有个圆心怎么算呢
+    # out = test(current_direction_type=3, start_x=dumpCD[0], start_y=dumpCD[1], target_point_name='A.30')
+    print(out)
+    # --- test ---
+    # out = get_nearest_point_name_from_pot_point(dumpCD[0], dumpCD[1])
+    # print(out)

+ 47 - 0
project-responder-hs/Dockerfile

@@ -0,0 +1,47 @@
+FROM ubuntu:20.04
+ENV DEBIAN_FRONTEND noninteractive
+
+RUN echo "Install Python v3.8:" \
+    && apt-get update \
+    && apt-get install -y  \
+        python3-dev \
+        python3-pip \
+        python3-setuptools \
+        python3-wheel \
+    && pip3 config set global.index-url https://mirror.baidu.com/pypi/simple \
+    && pip3 config set global.extra-index-url https://pypi.tuna.tsinghua.edu.cn/simple \
+    && pip3 install --upgrade --quiet pip setuptools \
+    && python3 --version
+
+# --- install requirements ---
+RUN echo "Install Python Requirements:" \
+    && pip3 install --default-timeout=1800 --no-cache-dir \
+        # --- for base --- \
+        cython==3.0.0a9 \
+        pyinstaller==4.10 \
+        # --- for libraries --- \
+        pycrypto==2.6.1 \
+        paramiko==2.7.2 \
+        apscheduler==3.7.0 \
+        # --- for client --- \
+        requests==2.25.1 \
+        redis==3.5.3 \
+        pymongo==3.11.2 \
+        influxdb==5.3.1 \
+        pymysql==0.9.3 \
+        peewee==3.17.0 \
+        SQLAlchemy==1.4.30 \
+        paho-mqtt==1.6.1 \
+        # --- for server --- \
+        typesystem==0.2.5 \
+        aiohttp==3.7.3 \
+        responder==2.0.7 \
+        uvloop==0.14.0 \
+        uvicorn==0.13.2 \
+        supervisor==4.2.1 \
+        websocket-client==0.58.0 \
+    && echo "End."
+
+RUN echo "Debug Tools:" \
+    && apt-get update \
+    && apt-get install -y inetutils-ping iproute2 net-tools wget unzip git bash

+ 12 - 0
project-responder-hs/README-usage.bash

@@ -0,0 +1,12 @@
+## NOTE
+
+# 一、构建操作
+# 构建并启动
+echo "BEGIN:" \
+&& project_path="/home/ubuntu/repositories/repositories/casperz.py-project/project-responder-hs" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose.yml down \
+&& sudo docker-compose --file compose.yml up --detach --build \
+&& sudo docker-compose --file compose.yml logs --follow
+# 日常调试命令
+sudo docker restart sri-module-hs02 && sudo docker logs -f sri-module-hs02

+ 67 - 0
project-responder-hs/api/Command.py

@@ -0,0 +1,67 @@
+from hub import methods, Global
+
+
+class Command(object):
+
+    @classmethod
+    def cmd001(cls, **params):
+        """
+        url:
+            http://192.168.30.13:8892/api?module=Command&method=cmd001&version_id=d986be1
+            http://192.168.1.33:8892/api?module=Command&method=cmd001&version_id=d986be1
+        cmd:
+            echo "版本:2021年4月13日 13:58:11" \
+            && version_id="2d796e9" \
+            && image_name="39.99.150.29:9999/aibox-component-dashboard-packer:1.0.1" \
+            && script_dir="/home/server/projects/taiwuict/taiwuict-aibox/developer-operation/l1" \
+            && sudo docker run --interactive --volume /home:/home ${image_name} /bin/bash ${script_dir}/deployer-update_by_commit.sh ${version_id} \
+            && sudo bash ${script_dir}/07.restart_service.sh
+        """
+        version_id = params.get('version_id')
+        ssh = SSHClient('172.18.0.1', 22, 'server', 'server')
+        command = str()
+        command += f"version_id=\"{version_id}\""
+        command += f" && "
+        command += f"image_name=\"39.99.150.29:9999/aibox-component-dashboard-packer:1.0.1\""
+        command += f" && "
+        command += f"script_dir=\"/home/server/projects/taiwuict/taiwuict-aibox/developer-operation/l1\""
+        command += f" && "
+        command += r"sudo docker login -u admin -p admin http://39.99.150.29:9999"
+        command += f" && "
+        command += r"sudo docker run --interactive --volume /home:/home ${image_name} /bin/bash "
+        command += r"${script_dir}/deployer-update_by_commit.sh ${version_id}"
+        command += f" && "
+        command += r"sudo bash ${script_dir}/07.restart_service.sh"
+        command += f" && "
+        command += r"sudo chmod -R 777 /home/server/projects/taiwuict/taiwuict-aibox"
+        methods.debug_log(f"Command.cmd001", f"1: {command}")
+        ssh.run_command(command)
+        return dict(code=0)
+
+    @classmethod
+    def cmd002(cls, **params):
+        """
+        url:
+            http://192.168.30.14:8892/api?module=Command&method=cmd002
+        cmd:
+            echo "版本:2021年4月13日 13:58:11" \
+            && image_name="39.99.150.29:9999/aibox-component-dashboard-packer:1.0.1" \
+            && script_dir="/home/server/projects/taiwuict/taiwuict-aibox/developer-operation/l1" \
+            && sudo docker run --interactive --volume /home:/home ${image_name} /bin/bash ${script_dir}/deployer-update_pth.sh \
+            && sudo docker restart node_yolo
+        """
+        ssh = Global.SSHClient('172.18.0.1', 22, 'server', 'server')
+        command = str()
+        command += f"image_name=\"39.99.150.29:9999/aibox-component-dashboard-packer:1.0.1\""
+        command += f" && "
+        command += f"script_dir=\"/home/server/projects/taiwuict/taiwuict-aibox/developer-operation/l1\""
+        command += f" && "
+        command += r"sudo docker login -u admin -p admin http://39.99.150.29:9999"
+        command += f" && "
+        command += r"sudo docker run --interactive --volume /home:/home ${image_name} /bin/bash "
+        command += r"${script_dir}/deployer-update_pth.sh"
+        command += f" && "
+        command += r"sudo docker restart node_yolo"
+        methods.debug_log(f"Command.cmd002", f"1: {command}")
+        ssh.run_command(command)
+        return dict(code=0)

+ 58 - 0
project-responder-hs/api/Key.py

@@ -0,0 +1,58 @@
+from hub import methods, Global
+
+
+class Key(object):
+
+    @classmethod
+    def create_key(cls, **params):
+        """
+        生成key
+        """
+        # --- check ---
+        key_path = '/home/server/key'
+        if methods.is_file(key_path):
+            methods.remove_file(key_path)
+
+        # --- get serial ---
+        ssh = Global.SSHClient('172.18.0.1', 22, 'server', 'server')
+        serial = ssh.run_command("cat /sys/firmware/devicetree/base/serial-number").strip('\x00')
+
+        # --- generate salt ---
+        salt = methods.now_ts()
+        salt = methods.reverse(str(salt))
+        salt = 0
+
+        # --- generate key ---
+        keys = methods.text_to_b64(f"{serial}|{salt}")
+
+        # --- storage env_key ---
+        methods.run_command(f"echo \"{keys}\" > {key_path}", callback=True)
+        return dict(code=0, data=keys)
+
+    @classmethod
+    def verify_key(cls, **params):
+        """
+        验证key
+        """
+        # --- check ---
+        if not methods.is_file('/home/server/key'):
+            return dict(code=1, is_verified=False)
+
+        # --- get key ---
+        keys = methods.read_text('/home/server/key')
+        keys = methods.b64_to_text(keys)
+
+        # --- check again ---
+        if not keys:
+            return dict(code=2, is_verified=False)
+        if len(keys.split('|')) != 2:
+            return dict(code=3, is_verified=False)
+        serial_1, _ = keys.split('|')
+
+        # --- check again ---
+        ssh = Global.SSHClient('172.18.0.1', 22, 'server', 'server')
+        serial_2 = ssh.run_command("cat /sys/firmware/devicetree/base/serial-number").strip('\x00')
+        if serial_1 != serial_2:
+            return dict(code=4, is_verified=False)
+
+        return dict(code=0, is_verified=True)

+ 515 - 0
project-responder-hs/api/TEST.py

@@ -0,0 +1,515 @@
+from hub import methods, Global, numpy_method, camera_handle
+from factories.line_manage import LineManage
+
+import base64
+import cv2
+import os
+
+
+class TEST(object):
+
+    @classmethod
+    def test002(cls, **params):
+        """
+        """
+        try:
+
+            # --- define ---
+            run_at = methods.now_ts()
+            condition = {
+                'face_name': {'$ne': None},
+            }
+            total = Global.mdb.get_count('Face', condition)
+            items = Global.mdb.filter('Face', condition)
+            count = 0
+
+            for item in items:
+                # --- log ---
+                count += 1
+                methods.debug_log('TEST.test002', f"m-24: {count}/{total}")
+
+                # --- check ---
+                face_image_path = item.get('face_feature_info_list')[0].get('face_image_path')
+                face_image_path = f"{face_image_path[:-4]}-fake.jpg"
+                if not os.path.isfile(face_image_path):
+                    continue
+
+                # --- 提取特征 ---
+                image_array = cv2.imread(face_image_path)
+                result = Global.scrfd_agent.inference_with_image_array(image_array)
+                face_image = result[0].get('align_face')
+                face_features = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+                # --- check null ---
+                if face_features is None:
+                    methods.debug_log('TEST.test002', f"m-46: face_features is None! | {face_image_path}")
+                    continue
+
+                # --- update ---
+                face_uuid = str(item.get('_id'))
+                face_feature_info_list = item.get('face_feature_info_list')
+                face_feature_info_list.append(
+                    {
+                        'face_object_confidence': 1.0,
+                        'face_features': methods.pickle_dumps(face_features),
+                        'face_image_path': face_image_path,
+                        'detector_name': 'scrfd',
+                        'mask_on_face_is': False,
+                    }
+                )
+                Global.mdb.update_one_by_id('Face', face_uuid, {'face_feature_info_list': face_feature_info_list})
+                methods.debug_log('TEST.test002', f"m-62: face_image_path -> {face_image_path} | {face_uuid}")
+
+            # --- log ---
+            methods.debug_log('TEST.test002', f"m-32: time use {round(methods.now_ts() - run_at, 2)}s, "
+                                              f"update count is {count}")
+
+            return dict(code=0, details=f"end.")
+
+        except Exception as exception:
+            methods.debug_log('TEST.test002', f"m-84: exception | {exception}")
+            methods.debug_log('TEST.test002', f"m-84: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test001(cls, **params):
+        """批量导入人脸特征数据"""
+        try:
+            # --- define ---
+            api = Global.get_yzw_client()
+            db1 = Global.get_mariadb_client()
+            db2 = Global.get_mongodb_client()
+            run_at = methods.now_ts()
+
+            # --- get mariadb.worker_info ---
+            count = 0
+            items = db1.get_all('worker_info')
+            for item in items:
+
+                # --- debug log ---
+                count += 1
+                if count % 1000 == 0:
+                    methods.debug_log('TEST.test001', f"count: {count}")
+
+                # --- check null ---
+                if not item.image_path:
+                    continue
+
+                # --- set tags if not exists ---
+                data = db2.get_one(Global.dataset_name, {'prc_id': item.worker_id})
+                project_name_tags = data.get('project_name_tags', list())
+                worker_type_tags = data.get('worker_type_tags', list())
+                if item.project_name and item.project_name not in project_name_tags:
+                    project_name_tags.append(item.project_name)
+                if item.worker_type and item.worker_type not in worker_type_tags:
+                    worker_type_tags.append(item.worker_type)
+
+                # --- set face if not exists ---
+                face_features = data.get('face_features')
+                if not face_features:
+
+                    # --- check null ---
+                    image_bytes = api.get_image(item.image_path)
+                    if not image_bytes:
+                        # methods.debug_log('TEST.test001', f"d1: {item.worker_id}")
+                        continue
+
+                    # --- get face features ---
+                    face_features = DetectFaceEngine.get_face_features_by_image_bytes(image_bytes)
+                    # methods.debug_log('TEST.test001', f"d1: {face_features}")
+
+                # --- check null ---
+                if not face_features:
+                    # methods.debug_log('TEST.test001', f"d1: {item.worker_id}")
+                    continue
+
+                # --- set mongodb.VisitorInfo ---
+                unique_dict = {
+                    'prc_id': item.worker_id,
+                }
+                update_dict = {
+                    'face_name': item.worker_name,
+                    'face_path': f"https://lwres.yzw.cn/{item.image_path}",
+                    'face_features': methods.pickle_dumps(face_features),
+                    'project_name_tags': project_name_tags,
+                    'worker_type_tags': worker_type_tags,
+                }
+                db2.update_one(Global.dataset_name, unique_dict, update_dict)
+                # break
+
+            methods.debug_log('TEST.test001', f"t1: {methods.now_ts() - run_at}s")
+            return dict(code=0, details=f"end.")
+
+        except Exception as exception:
+            methods.debug_log('TEST.test001', f"m-84: exception | {exception}")
+            methods.debug_log('TEST.test001', f"m-84: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test004(cls, **params):
+        """更新 mongo.VisitorTags 表数据"""
+        run_at = methods.now_ts()
+        try:
+
+            # --- update mongodb.VisitorInfo ---
+            mdb = Global.get_mongodb_client()
+            for item in mdb.get_all(Global.dataset_name):
+
+                # --- update ProjectName ---
+                project_name_tags = item.get('project_name_tags', [])
+                for project_name in project_name_tags:
+                    unique_dict = {
+                        'tag_type': 'ProjectName',
+                        'tag_name': project_name,
+                    }
+                    update_dict = {}
+                    mdb.update_one('VisitorTags', unique_dict, update_dict)
+
+                # --- update ProjectName ---
+                worker_type_tags = item.get('worker_type_tags', [])
+                for worker_type in worker_type_tags:
+                    unique_dict = {
+                        'tag_type': 'WorkerType',
+                        'tag_name': worker_type,
+                    }
+                    update_dict = {}
+                    mdb.update_one('VisitorTags', unique_dict, update_dict)
+
+            # --- todo 遍历 VisitorTags 表 然后更新每条数据的 face_amout 字段 统计每个标签所属的人数
+
+            methods.debug_log('TEST.test004', f"m1: {methods.now_ts() - run_at}s")
+            return dict(code=0)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test004', f"m-124: exception | {exception}")
+            methods.debug_log('TEST.test004', f"m-124: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test006(cls, **params):
+        """
+        crop_img=np.array(faces[i]) # 每张图中的单个人脸图像
+        cv2.imwrite('crop.jpg',crop_img)
+        """
+        try:
+            # --- define ---
+            mdb = Global.get_mongodb_client()
+
+            item = mdb.get_one_by_id('Face', '622fffd65bda389a51c96c24')
+            face_features_1 = methods.pickle_loads(item.get('face_features'))
+
+            item = mdb.get_one_by_id('Face', '622fff612ae2021a236e2c90')
+            face_features_2 = methods.pickle_loads(item.get('face_features'))
+
+            dist = Global.arcface_agent.compare_faces_by_normalization(face_features_1, face_features_2)
+            methods.debug_log('TEST.test006', f"m-196: dist: {dist}")
+            return dict(code=0, details=f"End.", dist=dist)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test006', f"m-124: exception | {exception}")
+            methods.debug_log('TEST.test006', f"m-124: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test007(cls, **params):
+        """url测试websocket在线信息"""
+        try:
+            line_total = LineManage.get_line_total()
+            line_state = LineManage.get_line_state()
+            return dict(code=0, line_total=line_total, line_state=line_state)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test007', f"m-161: exception | {exception}")
+            methods.debug_log('TEST.test007', f"m-161: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test008(cls, **params):
+        """"""
+        try:
+            # --- define ---
+            api = Global.cscec_agent()
+
+            # --- get face b64 ---
+            file_path = f"/home/server/resources/vms-files/2021-0826-160627-600346.pickle"
+            image = methods.load_pickle_file(file_path)  # 解压
+            frame = numpy_method.to_array(image)  # list to numpy array
+            _, image = cv2.imencode('.jpg', frame)
+            base64_data = base64.b64encode(image)  # byte to b64 byte
+            s = base64_data.decode()  # byte to str
+            face_image_b64 = f'data:image/jpeg;base64,{s}'
+
+            # --- test push_face ---
+            methods.debug_log('TEST.test008', f"m-236: {type(methods.now_string())}")
+            api.push_face(face_image_b64=face_image_b64, now_at=methods.now_string())
+
+            # --- test push_face_log ---
+            api.push_face_log()
+
+            # --- test ---
+            api.pull_alarm_list()
+            api.pull_alarm_group_list()
+
+            return dict(code=0, details=f"end.")
+
+        except Exception as exception:
+            methods.debug_log('TEST.test008', f"m-196: exception | {exception}")
+            methods.debug_log('TEST.test008', f"m-196: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test009(cls, **params):
+        """"""
+        try:
+            file_path = '/home/server/resources/vms-files/2021-0822-161705-929476.pickle'
+            image = methods.load_pickle_file(file_path)  # 解压
+            frame = numpy_method.to_array(image)  # list to numpy array
+            _, image = cv2.imencode('.jpg', frame)
+            base64_data = base64.b64encode(image)  # byte to b64 byte
+            s = base64_data.decode()  # byte to str
+            face_image_b64 = f'data:image/jpeg;base64,{s}'
+            return dict(code=0, face_image_b64=face_image_b64)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test009', f"m-214: exception | {exception}")
+            methods.debug_log('TEST.test009', f"m-214: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test011(cls, **params):
+        """
+        下载语音文件
+        http://192.168.30.13:8800/api?module=TEST&method=test011&mp3_name=2021-08-17-14-38-49-223380.mp3
+        http://192.168.30.13:8800/api?module=TEST&method=test011&mp3_name=2021-08-17-14-59-15-665168.mp3
+        """
+        try:
+            # --- check ---
+            mp3_name = params.get('mp3_name')
+            if not mp3_name:
+                return dict(code=1, details=f"something is wrong.")
+
+            # --- check ---
+            file_path = f"/home/server/resources/mp3-files/{mp3_name}"
+            if not methods.is_file(file_path):
+                return dict(code=2, details=f"something is wrong.")
+
+            return methods.read_bytes(file_path)
+
+        except Exception as exception:
+
+            methods.debug_log('Task.test011', f"m-239: exception | {exception}")
+            methods.debug_log('Task.test011', f"m-239: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test012(cls, **params):
+        """
+        录入人脸图片
+        """
+        try:
+            # --- 提取特征 ---
+            # file_name = 'IMG_20220315_101522.jpg'
+            # file_name = 'IMG_20220315_101649.jpg'
+            # file_name = '2022-0324-154547-015729.jpg'
+            # file_name = 'bing.jpg'
+            file_name = 'qiang.jpg'
+            image_array = cv2.imread(f'/home/server/resources/TestData/2022/0315/{file_name}')
+
+            # --- 剪裁人脸 ---
+            # inference_result = Global.scrfd_agent.inference(image_array)
+            # image_array = inference_result[0].get('face_image')
+
+            # --- 特征提取 ---
+            face_features = Global.arcface_agent.get_face_features_normalization_by_image_array(image_array)
+
+            # --- save file ---
+            save_at = methods.now_string('%Y-%m%d-%H%M%S-%f')
+            face_file_path = f"/home/server/resources/vms-files/{save_at}.jpg"
+            cv2.imwrite(face_file_path, image_array)
+
+            # --- save mongodb ---
+            """
+            Face: 陌生人脸表
+            Face.face_features: 人脸特征
+            Face.face_image_path: 人脸图像文件路径
+            Face.create_at: 录入时间
+            Face.prc_id: 身份证号
+            Face.face_image_url: 人脸地址
+            Face.face_name: 人员姓名
+            """
+            unique_dict = {
+                'prc_id': file_name,
+            }
+            update_dict = {
+                'face_features': methods.pickle_dumps(face_features),
+                'face_image_path': face_file_path,
+                'create_at': methods.now_ts(),
+                'face_image_url': None,
+                # 'face_name': 'zhang',
+                # 'face_name': 'bing',
+                'face_name': '陆强',
+            }
+            Global.mdb.update_one('Face', unique_dict, update_dict)
+
+            return dict(code=0, details=f"end.")
+
+        except Exception as exception:
+
+            methods.debug_log('TEST.test012', f"m-297: exception | {exception}")
+            methods.debug_log('TEST.test012', f"m-297: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test013(cls, **params):
+        """对比人脸图像的相似度"""
+        try:
+            # --- test 1 ---
+
+            # face_path = '/home/server/resources/vms-files/2022-0507-152213-741783.jpg'
+            # result = Global.scrfd_agent.inference(face_path)
+            # face_image = result[0].get('face_image')
+            # face_features_0 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # face_path = '/home/server/resources/TestData/2022/0513/lq1.jpg'
+            # result = Global.scrfd_agent.inference(face_path)
+            # face_image = result[0].get('face_image')
+            # face_features_1 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # face_path = '/home/server/resources/TestData/2022/0513/lq2.jpg'
+            # result = Global.scrfd_agent.inference(face_path)
+            # face_image = result[0].get('face_image')
+            # face_features_2 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # dist_1 = Global.arcface_agent.compare_faces_by_normalization(face_features_0, face_features_1)
+            # dist_2 = Global.arcface_agent.compare_faces_by_normalization(face_features_0, face_features_2)
+
+            # --- test 2 ---
+
+            # face_path = '/home/server/resources/TestData/2022/0531-1/001.jpg'  # 0.7584765553474426
+            # face_path = '/home/server/resources/TestData/2022/0531-1/002.jpg'  # 0.8457706868648529
+            face_path = '/home/server/resources/TestData/2022/0531-1/004.jpg'  # 0.8554660677909851 0.8809504210948944
+            # face_path = '/home/server/resources/TestData/2022/0531-1/009.jpg'  # 0.7084800899028778
+            image_array = cv2.imread(face_path)
+            result = Global.scrfd_agent.inference_with_image_array(image_array)
+            # face_image = result[0].get('raw_face')
+            face_image = result[0].get('align_face')
+            face_features_1 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # face_path = '/home/server/resources/TestData/2022/0531-1/101.jpg'  # 0.7629551887512207
+            # face_path = '/home/server/resources/TestData/2022/0531-1/102.jpg'  # 0.8441838622093201
+            face_path = '/home/server/resources/TestData/2022/0531-1/104.jpg'  # 0.8168102502822876 0.8709447979927063
+            # face_path = '/home/server/resources/TestData/2022/0531-1/109.jpg'  # 0.6636235117912292
+            image_array = cv2.imread(face_path)
+            result = Global.scrfd_agent.inference_with_image_array(image_array)
+            # face_image = result[0].get('raw_face')
+            face_image = result[0].get('align_face')
+            face_features_2 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # face_path = '/home/server/resources/TestData/2022/0531-1/201.jpg'
+            # face_path = '/home/server/resources/TestData/2022/0531-1/202.jpg'
+            face_path = '/home/server/resources/TestData/2022/0531-1/204.jpg'
+            # face_path = '/home/server/resources/TestData/2022/0531-1/209.jpg'
+            image_array = cv2.imread(face_path)
+            result = Global.scrfd_agent.inference_with_image_array(image_array)
+            # face_image = result[0].get('raw_face')
+            face_image = result[0].get('align_face')
+            face_features_3 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            dist_1 = Global.arcface_agent.compare_faces_by_normalization(face_features_1, face_features_3)
+            dist_2 = Global.arcface_agent.compare_faces_by_normalization(face_features_2, face_features_3)
+
+            return dict(code=0, dist_1=dist_1, dist_2=dist_2)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test013', f"m-214: exception | {exception}")
+            methods.debug_log('TEST.test013', f"m-214: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test014(cls, **params):
+        """
+        对比人脸图像的相似度
+        face-uuid
+        62a008b32bb1ca6081d36b1a
+        base
+        629eb0fb1ef13cc2688962a4
+        """
+        try:
+
+            # item = Global.mdb.get_one_by_id('Face', '627621d4013c78548cee0020')  # 于佑飞
+            # item = Global.mdb.get_one_by_id('Face', '62761dc5013c78548cedfff4')  # 王彦杰
+            item = Global.mdb.get_one_by_id('Face', '629eb0fb1ef13cc2688962a4')  # test
+            face_features_0 = methods.pickle_loads(item.get('face_feature_info_list')[0].get('face_features'))
+
+            item = Global.mdb.get_one_by_id('Face', '62a008b32bb1ca6081d36b1a')  # test
+            face_features_1 = methods.pickle_loads(item.get('face_feature_info_list')[0].get('face_features'))
+
+            # face_path = '/home/server/resources/TestData/2022/0527/1653640682.jpg'
+            # face_path = '/home/server/resources/TestData/2022/0527/1653640715.jpg'
+            # image_array = cv2.imread(face_path)
+            # result = Global.scrfd_agent.inference_with_image_array(image_array)
+            # # face_image = result[0].get('raw_face')
+            # face_image = result[0].get('align_face')
+            # face_features_1 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            dist_1 = Global.arcface_agent.compare_faces_by_normalization(face_features_0, face_features_1)
+            return dict(code=0, dist_1=dist_1)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test014', f"m-214: exception | {exception}")
+            methods.debug_log('TEST.test014', f"m-214: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test015(cls, **params):
+        """
+        检索相似人脸
+        """
+        try:
+
+            # face_path = '/home/server/resources/TestData/2022/0527/1653640682.jpg'
+            face_path = '/home/server/resources/TestData/2022/0629/20220629090605.jpg'
+            image_array = cv2.imread(face_path)
+            result = Global.scrfd_agent.inference_with_image_array(image_array)
+            face_image = result[0].get('align_face')
+            face_features_0 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # --- fill face_dict ---
+            face_dict = dict()
+            condition = {
+                'face_name': {'$ne': None},  # 只获取人为标注的底库
+            }
+            items = Global.mdb.filter('Face', condition)
+            for index, item in enumerate(items):
+
+                # --- define ---
+                face_uuid = str(item.get('_id'))
+                face_dict[face_uuid] = list()
+
+                # --- check ---
+                face_feature_info_list = item.get('face_feature_info_list')
+                if not face_feature_info_list:
+                    continue
+
+                # --- fill ---
+                for face_feature_info in face_feature_info_list:
+                    face_features = face_feature_info.get('face_features')
+                    if not face_features:
+                        continue
+                    if methods.pickle_loads(face_features) is None:
+                        continue
+                    d1 = methods.pickle_loads(face_features), face_feature_info.get('face_image_path')
+                    face_dict[face_uuid].append(d1)
+
+            run_at = methods.now_ts(unit='ms')
+            face_uuid, face_dist, base_face_image_path = Global.arcface_agent.search_face_v2(face_features_0, face_dict)
+            use_time = round((methods.now_ts(unit='ms') - run_at), 2)
+            methods.debug_log('TEST.test015', f"m-439: use time {use_time}ms")
+
+            return dict(code=0, face_uuid=face_uuid, face_dist=face_dist, base_face_image_path=base_face_image_path,
+                        use_time=use_time)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test015', f"m-214: exception | {exception}")
+            methods.debug_log('TEST.test015', f"m-214: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")

+ 222 - 0
project-responder-hs/api/Task.py

@@ -0,0 +1,222 @@
+from hub import methods, Global
+
+import cv2
+import base64
+
+
+class Task(object):
+
+    @classmethod
+    def task002(cls, **params):
+        """获取特征值接口"""
+        run_at = methods.now_ts()
+        try:
+
+            # --- get face_features ---
+            call_id = params.get('call_id')
+            save_is = params.get('save_is')
+            rdb = Global.get_redis_client()
+            image_bytes = rdb.get_one(key=call_id)
+
+            # --- 人脸裁剪逻辑 ---
+            # methods.debug_log('Task.task002', f"m-24: {type(image_bytes)}")
+            image_array = Global.scrfd_agent.image_bytes_to_image_array(image_bytes)
+            # methods.debug_log('Task.task002', f"m-24: {type(image_array)}")
+            # inference_result = Global.scrfd_agent.inference(image_array)
+            inference_result = Global.scrfd_agent.inference_with_image_array(image_array)
+
+            # --- save file ---
+            if save_is:
+                save_at = methods.now_string('%Y-%m%d-%H%M%S-%f')
+                frame = Global.arcface_agent.image_bytes_to_image_array(image_bytes)
+                face_file_path = f"/home/server/resources/vms-files/{save_at}-raw.jpg"
+                cv2.imwrite(face_file_path, frame)
+            else:
+                face_file_path = ''
+
+            # --- check ---
+            if not inference_result:
+                return dict(code=1, details=f"something is wrong.")
+
+            # --- 歪脸矫正 ---
+            face_image = inference_result[0].get('align_face')
+            probability = 0.0
+
+            # --- 提取人脸特征 ---
+            face_features = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # --- check ---
+            if face_features is None:
+                return dict(code=2, details=f"something is wrong.")
+
+            # --- debug to close --- todo 存在歪脸矫正后,图像图像变模糊的问题
+            # save_at = methods.now_string('%Y-%m%d-%H%M%S-%f')
+            # face_file_path_x = f"/home/server/resources/vms-files/{save_at}-align.jpg"
+            # cv2.imwrite(face_file_path_x, face_image)
+
+            # --- 增加戴口罩人脸底库特征与图片 ---
+            masked_face_image = Global.face_to_mask_face.add_mask_one(face_image)
+            if masked_face_image is not None:
+                face_features_2 = Global.arcface_agent.get_face_features_normalization_by_image_array(masked_face_image)
+            else:
+                face_features_2 = face_features
+                masked_face_image = face_image
+
+            # --- save file ---
+            if save_is:
+                save_at = methods.now_string('%Y-%m%d-%H%M%S-%f')
+                face_file_path_2 = f"/home/server/resources/vms-files/{save_at}-masked.jpg"
+                cv2.imwrite(face_file_path_2, masked_face_image)
+            else:
+                face_file_path_2 = ''
+
+            # --- update call_id value ---
+            rdb.set_one(key=call_id, data=(face_features, probability, face_file_path,
+                                           face_features_2, probability, face_file_path_2), expire_time=600)
+            return dict(code=0, details=f"congratulations. | use time {methods.now_ts() - run_at}s")
+
+        except Exception as exception:
+
+            methods.debug_log('Task.task002', f"m-76: exception | {exception}")
+            methods.debug_log('Task.task002', f"m-76: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def task003(cls, **params):
+        """
+        支持8301接口
+        """
+        run_at = methods.now_ts()
+        try:
+            # --- get data ---
+            call_id = params.get('call_id')
+            rdb = Global.get_redis_client()
+            input_data = rdb.get_one(key=call_id)
+
+            # --- 人脸裁剪逻辑 ---
+            image_array = Global.scrfd_agent.image_bytes_to_image_array(input_data.get('image_bytes'))
+            inference_result = Global.scrfd_agent.inference_with_image_array(image_array)
+
+            # --- 歪脸矫正 ---
+            face_image = inference_result[0].get('align_face')
+
+            # --- 提取人脸特征 ---
+            face_features = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+            # methods.debug_log('Task.task003', f"m-105: face_features | {face_features}")
+
+            # --- fill face_type_name_dict ---
+            """
+            face_type_name_dict = {<type_uuid>: <name>}
+            """
+            face_type_name_dict = dict()
+            for item in Global.mdb.get_all('FaceType'):
+                uuid = str(item.get('_id'))
+                face_type_name_dict[uuid] = item.get('name')
+
+            # --- fill d5 ---
+            d5 = dict()  # {<face_uuid>: {info}}
+            for item in Global.mdb.get_all('Face'):
+                face_uuid = str(item.get('_id'))
+                d5[face_uuid] = item.copy()
+
+            # --- fill d2 ---
+            """
+            FaceLog: 陌生人访问日志表
+            FaceLog.face_image_path: 抓拍人脸图像路径
+            FaceLog.face_probability: 人脸概率
+            FaceLog.base_face_uuid: 底库人脸id
+            FaceLog.base_face_image_path: 底库人脸图片路径
+            FaceLog.face_similarity: 对比相似度
+            FaceLog.count: 条目计数
+            """
+            d2 = list()
+            start_ts = methods.string_to_ts(f"{input_data.get('start_date_at')}", pattern='%Y-%m-%d %H:%M:%S')
+            end_ts = methods.string_to_ts(f"{input_data.get('end_date_at')}", pattern='%Y-%m-%d %H:%M:%S') + 1
+            items = list(Global.xdb.filter_by_time_range('FaceLog', start_ts, end_ts))
+            for item in items:
+
+                # --- check ---
+                base_face_uuid = item.get('base_face_uuid')
+                if base_face_uuid not in d5:
+                    continue
+
+                # --- check ---
+                # face = Global.mdb.get_one_by_id('Face', base_face_uuid)
+                face = d5.get(base_face_uuid)
+                face_features_1 = methods.pickle_loads(face.get('face_feature_info_list')[0].get('face_features'))
+                if face_features_1 is None:
+                    continue
+
+                # --- check ---
+                dist = Global.arcface_agent.compare_faces_by_normalization(face_features, face_features_1)
+                if dist < input_data.get('face_similarity'):
+                    continue
+
+                # --- fill ---
+                d4 = item, face, dist
+                d2.append(d4)
+
+            # --- fill d3 ---
+            d3 = list()
+            page = input_data.get('page')
+            size = input_data.get('size')
+            for item, face, dist in d2[(page - 1) * size: page * size]:
+
+                # --- fill d1 --- todo 返回数据 -> 抓拍照片、检索照片、相似度
+                d1 = {
+                    'face_uuid': item.get('base_face_uuid'),
+                    'face_name': face.get('face_name'),
+                    'record_at': methods.string_to_ts(item.get('time'), pattern='%Y-%m-%dT%H:%M:%S.%fZ'),
+                    'snap_face_image_b64': str(),  # 抓拍照片
+                    'base_face_image_b64': str(),  # 对比照片
+                    'face_type_name_list': list(),  # 类别名称
+                    'face_similarity': dist,  # 相似度
+                }
+                # methods.debug_log('actions.action_8201', f"m-86: d1 is {d1}")
+
+                # --- fill snap_face_image_b64 ---
+                file_path = item.get('face_image_path')
+                if file_path and methods.is_file(file_path):
+                    frame = cv2.imread(file_path)
+                    if frame is not None:
+                        _, image = cv2.imencode('.jpg', frame)
+                        base64_data = base64.b64encode(image)  # byte to b64 byte
+                        s = base64_data.decode()  # byte to str
+                        d1['snap_face_image_b64'] = f'data:image/jpeg;base64,{s}'
+
+                # --- fill base_face_image_b64 ---
+                # file_path = item.get('base_face_image_path')
+                # if file_path and methods.is_file(file_path):
+                #     frame = cv2.imread(file_path)
+                #     if frame is not None:
+                #         _, image = cv2.imencode('.jpg', frame)
+                #         base64_data = base64.b64encode(image)  # byte to b64 byte
+                #         s = base64_data.decode()  # byte to str
+                #         d1['base_face_image_b64'] = f'data:image/jpeg;base64,{s}'
+
+                # --- fill base_face_image_b64 ---
+                frame = Global.arcface_agent.image_bytes_to_image_array(input_data.get('image_bytes'))
+                if frame is not None:
+                    _, image = cv2.imencode('.jpg', frame)
+                    base64_data = base64.b64encode(image)  # byte to b64 byte
+                    s = base64_data.decode()  # byte to str
+                    d1['base_face_image_b64'] = f'data:image/jpeg;base64,{s}'
+
+                # --- fill face_type_name_list ---
+                face_type_uuid_list = face.get('face_type_uuid_list')
+                if face_type_uuid_list:
+                    d1['face_type_name_list'] = [face_type_name_dict.get(i)
+                                                 for i in face_type_uuid_list if face_type_name_dict.get(i)]
+
+                # --- append d1 ---
+                d3.append(d1)
+
+            # --- update call_id value ---
+            rdb.set_one(key=call_id, data=(d3, len(d2)), expire_time=600)
+            return dict(code=0, details=f"congratulations. | use time {methods.now_ts() - run_at}s")
+
+        except Exception as exception:
+
+            methods.debug_log('Task.task003', f"m-76: exception | {exception}")
+            methods.debug_log('Task.task003', f"m-76: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")

+ 36 - 0
project-responder-hs/api/url.py

@@ -0,0 +1,36 @@
+from api.Command import Command
+from api.Key import Key
+# from api.Task import Task
+# from api.TEST import TEST
+
+action_methods = {
+
+    # --- for command ---
+    'Command.cmd001': (Command, 'cmd001', 'NoBack'),  # update code by git commit
+    'Command.cmd002': (Command, 'cmd002', 'NoBack'),  # update pth by apache server
+    'Command.cmd003': (Command, 'cmd003', 'IsBack'),  # docker ps
+    'Command.cmd004': (Command, 'cmd004', 'IsBack'),  # docker images
+
+    # --- 初始化操作接口 ---
+    'Key.create_key': (Key, 'create_key', 'IsBack'),  # 生成key
+    'Key.verify_key': (Key, 'verify_key', 'IsBack'),  # 验证key
+
+    # --- for task ---
+    # 'Task.task002': (Task, 'task002', 'IsBack'),  # 接收 fastapi 发送来的 uuid 并计算人脸存储特征 同样提供判断该照片是否能用
+    # 'Task.task003': (Task, 'task003', 'IsBack'),  # 为8301接口提供业务支持
+
+    # --- for test ---
+    # 'TEST.test002': (TEST, 'test002', 'NoBack'),  # 刷超分人脸图片接口 将超分图片加入到底库中
+    # 'TEST.test001': (TEST, 'test001', 'NoBack'),  # 批量导入人脸特征数据 from mariadb to mongodb
+    # 'TEST.test004': (TEST, 'test004', 'NoBack'),  # 更新 mongo.VisitorTags 表数据
+    # 'TEST.test006': (TEST, 'test006', 'IsBack'),  # 对比人脸特征
+    # 'TEST.test007': (TEST, 'test007', 'IsBack'),  # 测试websocket在线信息
+    # 'TEST.test008': (TEST, 'test008', 'IsBack'),  # test
+    # 'TEST.test009': (TEST, 'test009', 'IsBack'),  # test
+    # 'TEST.test011': (TEST, 'test011', 'IsBack'),  # 下载语音文件
+    # 'TEST.test012': (TEST, 'test012', 'IsBack'),  # 录入人脸图片
+    # 'TEST.test013': (TEST, 'test013', 'IsBack'),  # 对比人脸图像的相似度
+    # 'TEST.test014': (TEST, 'test014', 'IsBack'),  # 对比人脸图像的相似度
+    # 'TEST.test015': (TEST, 'test015', 'IsBack'),  # 检索相似人脸
+
+}

+ 162 - 0
project-responder-hs/app.py

@@ -0,0 +1,162 @@
+# note: https://responder.kennethreitz.org/en/latest/search.html?q=resp.content
+from hub import methods
+
+
+def generate_app():
+    """"""
+    from api.url import action_methods
+    # from factories.line_manage import LineManage
+
+    # see: https://lzomedia.com/blog/host-fastapi-backend-api-and-react-app-frontend-locally/
+    # --- define middleware --- see: https://www.starlette.io/middleware/
+    # from starlette.applications import Starlette
+    # from starlette.middleware import Middleware
+    # # from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
+    # # from starlette.middleware.trustedhost import TrustedHostMiddlewar
+    # middleware = [
+    #     Middleware(
+    #         TrustedHostMiddleware,
+    #         allow_credentials=True,
+    #         allowed_hosts=['*'],
+    #         allow_methods=["*"],
+    #         allow_headers=["*"],
+    #     ),
+    #     # Middleware(HTTPSRedirectMiddleware)
+    # ]
+
+    # --- define app --- see: https://responder.kennethreitz.org/en/latest/tour.html#cors
+    import responder
+    app = responder.API(
+        # cors=True,
+        # allow_methods=['*'],
+        # allow_credentials=True,
+    )
+
+    async def coroutine_method(module, method, _params):
+        """协程方法"""
+        # run_at = time.time()
+        result = await getattr(module, method)(**_params)
+        # methods.debug_log(f"app.coroutine_method", f"use time {round(time.time() - run_at, 2)}s")
+        return result
+
+    def foreground_method(module, method, _params):
+        """等待返回"""
+        # run_at = time.time()
+        result = getattr(module, method)(**_params)
+        # methods.debug_log(f"app.foreground_method", f"use time {round(time.time() - run_at, 2)}s")
+        return result
+
+    @app.background.task
+    def background_method(module, method, _params):
+        """不等返回"""
+        # run_at = time.time()
+        result = getattr(module, method)(**_params)
+        # methods.debug_log(f"app.background_method", f"use time {round(time.time() - run_at, 2)}s")
+        return result
+
+    async def run_method(_method_key, _params):
+
+        if _method_key not in action_methods:
+            return dict(code=3, details=f"{_method_key} not found!")
+        else:
+            module, method, method_type = action_methods[_method_key]
+            del _params['module']
+            del _params['method']
+            if method_type == 'IsAsync':
+                return await coroutine_method(module, method, _params)
+            elif method_type == 'IsBack':
+                return foreground_method(module, method, _params)
+            elif method_type == 'NoBack':
+                return background_method(module, method, _params)
+
+    @app.route('/api')
+    async def web_api(request, response):
+        try:
+            if request.params:
+                params = dict(request.params.items())
+            else:
+                params = await request.media()
+
+            # --- todo 存在get请求只能接受一个参数的问题 ---
+            if 'mp3_name' in params:
+                params['module'] = 'TEST'
+                params['method'] = 'test011'
+
+            methods.debug_log('app.web_api', f"m-60: params is {params}")
+            if not params.get('module'):
+                response.media = dict(code=1, details=f"module is null!")
+            elif not params.get('method'):
+                response.media = dict(code=2, details=f"method is null!")
+            else:
+                method_key = f"{params.get('module')}.{params.get('method')}"
+                result = await run_method(method_key, params)
+                if result.__class__.__name__ == 'bytes':
+                    response.content = result
+                elif result.__class__.__name__ == 'str':
+                    response.text = result
+                elif result.__class__.__name__ == 'dict':
+                    response.media = result
+                elif result.__class__.__name__ == 'Future':
+                    response.media = dict(code=0, details=f"{result} is running.")
+                elif not result:
+                    response.media = dict(code=0, details=f"return is null.")
+                else:
+                    response.media = dict(code=0, details=f"return type is {result.__class__.__name__}.")
+
+        except Exception as exception:
+
+            methods.debug_log('app.web_api', f"m-86: exception | {exception}")
+            methods.debug_log('app.web_api', f"m-86: traceback | {methods.trace_log()}")
+            response.media = dict(code=-1, details=f"{methods.trace_log()}")
+
+    @app.route('/line', websocket=True)
+    async def line(ws):
+        """
+        let wsuri = `${location.protocol === 'https' ? 'wss' : 'ws'}://${location.host}:8801/line`;
+        connect_json_text = {
+            line_id: 连接id,暂为token
+        }
+        )
+        send_json_text_1 = {
+            face_uuid: 匹配到的人脸id
+            face_name: 人脸名称
+            input_face_b64: 抓拍图片(b64格式字符串)
+        }
+        """
+        try:
+            await ws.accept()
+            while True:
+
+                data = await ws.receive_text()  # 消息接受方法 receive_{text/json/bytes}
+                if not data:
+                    methods.debug_log('app.line', f"error code is 1!")
+                    break
+                if not methods.is_json(data):
+                    methods.debug_log('app.line', f"error code is 2!")
+                    break
+
+                data = methods.json_loads(data)
+                # methods.debug_log('app.line', f"data: {data}")
+                if not data.get('line_id'):
+                    methods.debug_log('api.line', f"error code is 3!")
+                    break
+
+                # --- check ---
+                if not data.get('line_id'):
+                    methods.debug_log('api.line', f"error code is 4!")
+                    break
+
+                # --- save ---
+                line_id = data.get('line_id')
+                # LineManage.line_dict[line_id] = ws
+
+        except Exception as exception:
+
+            if exception.__class__.__name__ == 'WebSocketDisconnect':
+                await ws.close()
+            else:
+                methods.debug_log('app.line', f"m-136: exception | {exception}")
+                methods.debug_log('app.line', f"m-136: traceback | {methods.trace_log()}")
+
+    # --- return ---
+    return app

+ 37 - 0
project-responder-hs/compose.yml

@@ -0,0 +1,37 @@
+version: '3.5'
+services:
+
+    sri-module-hs02:
+
+        # --- building ---
+        image: sri-module-hs02:2024
+        build:
+            context: ./
+            dockerfile: ./Dockerfile
+        environment:
+            TZ: Asia/Shanghai
+
+        # --- binding ---
+        volumes:
+            - /home:/home
+        networks:
+            - sri_network
+        ports:
+            - "9102:5173"
+
+        # --- running ---
+        container_name: sri-module-hs02
+
+        # --- for debug ---
+#        working_dir: /home/ubuntu/repositories/repositories/casperz.py-project/project-responder-hs 
+#        stdin_open: true
+#        tty: true
+
+        # --- for release ---
+        working_dir: /home/ubuntu/repositories/repositories/casperz.py-project/project-responder-hs 
+        command: bash run.sh
+        restart: always
+
+networks:
+    sri_network:
+        external: true

+ 21 - 0
project-responder-hs/hub.py

@@ -0,0 +1,21 @@
+import sys
+import importlib
+
+sys.path.append('../module-py')
+
+methods = importlib.import_module(f"xlib")
+
+
+class Global(object):
+    # --- 中间件服务器 ---
+    mdb = importlib.import_module(f"xclient.xmongo").Client(host='192.168.131.23', port=7030, database='hs',
+                                                            username='admin', password='admin')
+    mq01 = importlib.import_module(f"xclient.xmqtt").Client(host='192.168.131.23', port=41883)
+    mq02 = importlib.import_module(f"xclient.xmqtt").Client(host='192.168.131.23', port=41883)
+
+    # --- 插件 ---
+    aps = importlib.import_module(f"xpip.xapscheduler").APS(db_type='mongo',
+                                                            db_host='192.168.131.23',
+                                                            db_port=7030,
+                                                            username='admin', password='admin',
+                                                            database='hs', collection='LoopTask')

+ 26 - 0
project-responder-hs/main.py

@@ -0,0 +1,26 @@
+from app import generate_app
+app = generate_app()
+
+def main():
+    # 定时任务
+    # from lib.JobManage import JobManage
+    # JobManage.run()
+
+    # 监听mqtt消息服务
+    from unit.VehicleStateMessageListener import VehicleStateMessageListener
+    VehicleStateMessageListener.run_background(background_is=True)
+
+    # 监听mqtt消息服务
+    from unit.PotDataMessageListener import PotDataMessageListener
+    PotDataMessageListener.run_background(background_is=True)
+
+    # 给websocket发数据的
+    # from factories.line_manage import LineManage
+    # LineManage.run_background()
+
+    # websocket服务/api服务
+    app.run(address='0.0.0.0', port=5042, debug=True)
+
+
+if __name__ == '__main__':
+    main()

+ 5 - 0
project-responder-hs/run.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+echo "BEGIN:" \
+&& python3 main.py
+

+ 25 - 0
project-responder-hs/test/SRI2024032716-服务端mqtt话题接口文档.txt

@@ -0,0 +1,25 @@
+#车辆状态更新
+mqtt服务地址:192.168.131.23
+mqtt服务端口:41883
+mqtt话题:hs/vehicle/state
+mqtt消息说明:
+{
+    "address": "192.168.131.180",  # 车辆ip
+    "state": 1,  # 车辆状态 1 离线 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+    "direction": 15,  # 车头方向(场地坐标偏转角度)
+    "coordinate_x": 15,  # 当前车辆坐标
+    "coordinate_y": 15,  # 当前车辆坐标
+    "weight": 15,  # 负载重量
+}
+
+#渣包位置更新
+mqtt服务地址:192.168.131.23
+mqtt服务端口:41883
+mqtt话题:hs/pot/data
+mqtt消息说明:
+{
+  "pot_name": "M.24",  # 渣罐编号
+  "pot_x": 40,  # 坐标值
+  "pot_y": 50,  # 坐标值
+  "mark_pot_pose": 0.3  # 渣罐姿态
+}

+ 11 - 0
project-responder-hs/test/test_key_api.txt

@@ -0,0 +1,11 @@
+import requests
+
+# --- urls ---
+# service_url = 'http://192.168.30.115:18800'
+# service_url = 'http://192.168.30.115:8800'
+service_url = 'http://192.168.30.13:8800'
+# service_url = 'http://192.168.3.59:8802'
+
+# --- test Key ---
+# print(requests.post(url=f'{service_url}/api', json={'module': 'Key', 'method': 'create_key'}).text)
+# print(requests.post(url=f'{service_url}/api', json={'module': 'Key', 'method': 'verify_key'}).text)

+ 31 - 0
project-responder-hs/test/test_mqtt.py

@@ -0,0 +1,31 @@
+import paho.mqtt.client as mqtt
+import time
+import json
+
+client = mqtt.Client()
+client.connect(host='192.168.131.23', port=41883)
+
+
+def test(topic="hs/vehicle/state"):
+    data = {
+        "address": "192.168.131.180",  # 车辆ip
+        "state": 2,  # 车辆状态 1 离线 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+        "direction": 12,  # 车头方向(场地坐标偏转角度)
+    }
+    return topic, client.publish(topic, json.dumps(data))
+
+# def test(topic="hs/pot/data"):
+#     data = {
+#         "address": "192.168.131.180",  # 车辆ip
+#         "state": 2,  # 车辆状态 1 离线 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+#         "direction": 12,  # 车头方向(场地坐标偏转角度)
+#     }
+#     return topic, client.publish(topic, json.dumps(data))
+
+if __name__ == '__main__':
+    # --- test ---
+    while True:
+        topic, results = test()
+        result_code, message_id = results
+        print(f"#topic: {topic}, #result_code: {result_code}, #message_id: {message_id}")
+        time.sleep(3)

+ 41 - 0
project-responder-hs/test/test_test_api.txt

@@ -0,0 +1,41 @@
+import requests
+
+service_url = 'http://192.168.0.15:8802'
+# service_url = 'http://192.168.0.16:8802'
+# service_url = 'http://192.168.1.233:8802'
+
+# --- test test api ---
+print(requests.post(url=f'{service_url}/api', json={'module': 'TEST', 'method': 'test002'}).text)
+# print(requests.post(url=f'{service_url}/api', json={'module': 'TEST', 'method': 'test015'}).text)
+
+# --- test test001 导入人脸数据 ---
+# response = requests.post(url=f'{service_url}/api', json={'module': 'TEST', 'method': 'test001'})
+# print(response.text)
+
+# --- test test002 启动循环检查指定rtsp地址 ---
+# data = {
+#     'module': 'TEST',
+#     'method': 'test002',
+#     # 'rtsp_addr': 'rtsp://admin:DEVdev123@192.168.30.233:554/cam/realmonitor?channel=1&subtype=0',  # 大华
+#     'rtsp_addr': 'rtsp://admin:DEVdev123@192.168.30.235:554/h264/ch1/sub/av_stream',  # 海康
+# }
+# response = requests.post(url=f'{service_url}/api', json=data)
+# print(response.text)
+
+# --- test test006 ---
+# data = {
+#     'module': 'TEST',
+#     'method': 'test006',
+# }
+# response = requests.post(url=f'{service_url}/api', json=data)
+# print(response.text)
+
+# --- test test003 ---
+# data = {
+#     'module': 'TEST',
+#     'method': 'test003',
+#     'line_id': '112233',
+# }
+# response = requests.post(url=f'{service_url}/api', json=data)
+# print(response.text)
+

+ 50 - 0
project-responder-hs/unit/JobManage.py

@@ -0,0 +1,50 @@
+from hub import Global, methods
+
+
+class JobManage(object):
+    """
+
+    todo 实现每5分钟检查一下特定的ip是否通,是否可以ssh上去
+
+    """
+
+    # --- 接口认证信息 ---
+    # username = '500A7062'
+    # password = '198797#cjhxbin'
+
+    @classmethod
+    def run(cls):
+        """启动全部任务"""
+
+        # --- test ---
+        # Global.aps.create_job(func=cls.job101, trigger='interval', seconds=3)  # 循环测试
+        # Global.aps.create_job(func=cls.job101, trigger='date', run_date='2022-02-23 18:54:00')  # 定时测试
+        # Global.aps.create_job(func=cls.job301, trigger='interval', seconds=60)  # 循环测试
+        # Global.aps.create_job(func=cls.job20102, trigger='date', run_date='2022-07-28 17:15:30')  # 定时测试
+
+        # --- release ---
+        # Global.aps.create_job(func=cls.job101, trigger='cron', hour=22)  # 每天晚10点  release
+        # Global.aps.create_job(func=cls.job20102, trigger='interval', seconds=600)  # 每10分钟  release
+        Global.aps.create_job(func=cls.job301, trigger='interval', seconds=300)  # 每5分钟  release
+
+    @classmethod
+    def end(cls):
+        """暂停全部任务"""
+        Global.aps.pause_all()
+
+    @staticmethod
+    def job101():
+        """
+        每日22点清理30天之前的日志
+        """
+        # --- get list ---
+        log_file_dir = f"/home/server/logs"
+        file_path_list = methods.get_file_path_list(log_file_dir)
+        file_name_list = [i.split('/')[-1] for i in file_path_list]
+        # methods.debug_log('JobManage.job101.41', f"#file_name_list: {file_name_list}")
+
+        # --- cut list ---
+        for file_name in file_name_list[30:]:
+            file_path = f"/home/server/logs/{file_name}"
+            if methods.is_file(file_path):
+                methods.remove_file(file_path)

+ 60 - 0
project-responder-hs/unit/PotDataMessageListener.py

@@ -0,0 +1,60 @@
+from hub import methods, Global
+
+import threading
+import time
+import json
+
+
+class PotDataMessageListener(object):
+
+    @staticmethod
+    def decorate_method(client, userdata, message):
+        """消息处理方法"""
+        try:
+            # --- update ---
+            """
+            PotData: 渣罐数据
+            PotData.pot_name: 渣罐别称(例: M.24)
+            PotData.pot_x: 坐标值
+            PotData.pot_y: 坐标值
+            PotData.mark_pot_pose: 渣罐姿态
+            """
+            message = json.loads(message.payload)
+            unique_dict = {'pot_name': message.get('pot_name')}
+            update_dict = {
+                'pot_x': message.get('pot_x'),
+                'pot_y': message.get('pot_y'),
+                'mark_pot_pose': message.get('mark_pot_pose'),
+            }
+            Global.mdb.update_one('PotData', unique_dict, update_dict)
+            methods.debug_log(f"PotDataMessageListener.30", f"#message: {message}")
+
+        except Exception as exception:
+            methods.debug_log('PotDataMessageListener.33', f"#exception: {exception}")
+            methods.debug_log('PotDataMessageListener.33', f"#traceback: {methods.trace_log()}")
+
+    @classmethod
+    def start_check_loop(cls):
+        """启动循环"""
+        """
+        topic: hs/pot/data
+        message.name: 渣罐名称
+        message.pot_x: 坐标值
+        message.pot_y: 坐标值
+        message.mark_pot_pose: 渣罐姿态
+        """
+        Global.mq02.start_subscribe_loop(
+            decorate_method=PotDataMessageListener.decorate_method,
+            subscribe_topic='hs/pot/data'
+        )
+
+    @classmethod
+    def run_background(cls, background_is=True):
+        """启动现成"""
+        p1 = threading.Thread(target=cls.start_check_loop)
+        p1.start()
+
+
+if __name__ == '__main__':
+    # --- test ---
+    MessageListener.run_background()

+ 94 - 0
project-responder-hs/unit/VehicleStateMessageListener.py

@@ -0,0 +1,94 @@
+from hub import methods, Global
+
+import threading
+import time
+import json
+
+
+class VehicleStateMessageListener(object):
+
+    @staticmethod
+    def decorate_method(client, userdata, message):
+        """消息处理方法"""
+        try:
+            # --- check ---
+            message = json.loads(message.payload)
+            if 'direction' not in message:
+                methods.debug_log(f"VehicleStateMessageListener.18", f"#message: {message}")
+                return
+
+            # --- define ---
+            check_vehicle_direction = False
+            current_vehicle_direction = 0
+            direction = message.get('direction')
+            offset = 15  # 允许偏移角度(单位:度)
+            if 0 - offset < direction < 0 + offset:
+                check_vehicle_direction = True
+                current_vehicle_direction = 3
+            elif 90 - offset < direction < 90 + offset:
+                check_vehicle_direction = True
+                current_vehicle_direction = 12
+            elif 180 - offset < direction < 180 + offset:
+                check_vehicle_direction = True
+                current_vehicle_direction = 9
+            elif 270 - offset < direction < 270 + offset:
+                check_vehicle_direction = True
+                current_vehicle_direction = 6
+
+            # --- update ---
+            """
+            VehicleInfo: 渣包车信息表
+            VehicleInfo.uuid: 标识
+            VehicleInfo.name: 车辆名称
+            VehicleInfo.address: 车辆ip
+            VehicleInfo.state: 车辆状态 1 离线(默认值) 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+            VehicleInfo.check_vehicle_direction: 当前车头方向是否符合自动驾驶条件 True 符合 False 不符合(默认值)
+            VehicleInfo.current_vehicle_direction: 当前车头方向类型 3 围栏方向 9 渣场方向 12 维修间方向 6 维修间正对方向 0 未知(默认值)
+            VehicleInfo.check_vehicle_coordinate: 当前车辆坐标是否符合自动驾驶条件 True 符合 False 不符合(默认值)
+            VehicleInfo.current_vehicle_coordinate_x: 当前所在位置坐标 
+            VehicleInfo.current_vehicle_coordinate_y: 当前所在位置坐标 
+            VehicleInfo.current_vehicle_weight: 当前车辆负载重量 
+            """
+            unique_dict = {'address': message.get('address')}
+            update_dict = {
+                'state': message.get('state'),
+                'check_vehicle_direction': check_vehicle_direction,
+                'current_vehicle_direction': current_vehicle_direction,
+                'coordinate_x': message.get('coordinate_x'),
+                'coordinate_y': message.get('coordinate_y'),
+                'current_vehicle_weight': message.get('weight'),
+            }
+            Global.mdb.update_one('VehicleInfo', unique_dict, update_dict)
+            methods.debug_log(f"VehicleStateMessageListener.38", f"#message: {message}")
+
+        except Exception as exception:
+            methods.debug_log('VehicleStateMessageListener.40', f"#exception: {exception}")
+            methods.debug_log('VehicleStateMessageListener.40', f"#traceback: {methods.trace_log()}")
+
+    @classmethod
+    def start_check_loop(cls):
+        """启动循环"""
+        """
+        topic: hs/vehicle/state
+        message.address: 车辆ip
+        message.state: 车辆状态 1 离线 2 在线空闲 3 人工驾驶中 4 远程驾驶中 5 自动驾驶中
+        message.direction: 车头方向(场地坐标偏转角度) 
+        message.coordinate_x: 当前车辆坐标
+        message.coordinate_y: 当前车辆坐标
+        message.weight: 负载重量
+        """
+        Global.mq01.start_subscribe_loop(
+            decorate_method=VehicleStateMessageListener.decorate_method,
+            subscribe_topic='hs/vehicle/state'
+        )
+
+    @classmethod
+    def run_background(cls, background_is=True):
+        """启动现成"""
+        p1 = threading.Thread(target=cls.start_check_loop)
+        p1.start()
+
+
+if __name__ == '__main__':
+    # --- test ---
+    MessageListener.run_background()