.. _external: 外部程序 ======== .. _METLAB: https://dipaul.ru/catalog/element/metlab/ MeterVision 程序在与其他能够处理*机器视觉识别结果*的程序结合使用时可达到最佳效果。 例如,这类程序可以是 `METLAB`_ 或用户自编的软件。 为此,程序提供了两种方式将*识别结果*发送到外部程序 (``output`` 中的文件与 socket 服务器;在 :ref:`res` 中启用相应模式)。 同时,也可以通过外部程序以两种方式控制 MeterVision 的某些功能: 1. 通过 :ref:`catalog` 下的文本文件 ``input/command.txt``。 2. 通过 socket(需在 :ref:`settings` 中启用「通过 socket 传输结果」)——使用相同的命令语法; 每条命令单独一行;服务器回复 ``OK`` / ``ERROR: ...``(对 ``get`` 则返回结果)。 可在文件或 socket 中一次发送多条命令:每条命令单独占一行。 命令按从第一行到最后一行的顺序执行。 可使用的命令如下: 1. 关闭 MeterVision 程序 .. code-block:: exit 2. 保存 :ref:`screen` * 保存到默认文件夹(*output/screenshots/* 在 :ref:`catalog` 中),文件名使用默认名称(创建日期和时间) .. code-block:: screenshot * 保存到默认文件夹,但使用自定义文件名 .. code-block:: screenshot my_name.png * 保存到指定文件夹,并使用自定义文件名 .. code-block:: screenshot path/to/my/dir/my_name.png 3. 打开区域设置文件 .. code-block:: open path/to/my/dir/settings.mv 4. 固定背景(仅限指针方法) .. code-block:: fix_background n 其中 `n` 为目标区域编号 5. 取消背景固定(仅限指针方法) .. code-block:: unfix_background n 其中 `n` 为目标区域编号 6. 固定中心(仅限指针方法) .. code-block:: fix_center n 其中 `n` 为目标区域编号 7. 取消中心固定(仅限指针方法) .. code-block:: unfix_center n 其中 `n` 为目标区域编号 8. 忘记区域统计(仅限指针方法) .. code-block:: forget_stats n 其中 `n` 为目标区域编号 9. 忘记背景(仅限指针方法) .. code-block:: forget_background n 其中 `n` 为目标区域编号 10. 切换到其他刻度(仅限指针方法) .. code-block:: scale n m 其中 `n` 为目标区域编号,`m` 为要切换到的刻度编号 11. 切换结果显示模式(仅限指针方法) * 结果为刻度读数 .. code-block:: result_mode n scale * 结果为指针倾斜角(度) .. code-block:: result_mode n angle 其中 `n` 为目标区域编号 input/command.txt 文件示例 --------------------------- .. code-block:: screenshot my_name.png unfix_background 1 fix_center 1 scale 1 3 result_mode 1 angle 通过 socket 发送命令与获取结果 ------------------------------ 启用 socket 模式后(见 :ref:`res`),外部程序连接到 ``input/socket_config.txt`` 中的 host/port, 并发送以换行结尾的文本行。 命令与应答以换行符分隔。一次连接可发送多条命令;服务器对每条命令回复一行 (``get`` 的回复形如 ``{1: '24.10', 2: 'AC'}``,其他命令回复 ``OK`` 或 ``ERROR: ...``)。 * ``get`` 或 ``results`` — 获取当前识别结果(一次请求对应一次回复; 可在同一连接上重复调用任意次数)。 * 上表中的任意命令 — 执行操作;回复 ``OK`` 或 ``ERROR: ...``。 命令序列示例: .. code-block:: get fix_center 1 scale 1 2 获取结果示例(``examples/socket_results_example.py``)。 可在同一连接上多次调用 ``get_results()``: .. code-block:: python import socket import time class MeterVisionClient: def __init__(self, host='localhost', port=65432): self.host = host self.port = port self.sock = None self._buffer = '' def connect(self): """连接到 MeterVision 服务器。""" self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((self.host, self.port)) self._buffer = '' print(f"已连接到服务器 {self.host}:{self.port}") def get_results(self): """ 请求当前结果。可在同一连接上多次调用。 返回形如 "{1: '24.10', 2: 'AC'}" 的字符串。 """ if self.sock is None: raise RuntimeError('请先调用 connect()') self.sock.sendall(b'get\n') while '\n' not in self._buffer: chunk = self.sock.recv(4096) if not chunk: raise ConnectionError('服务器已关闭连接') self._buffer += chunk.decode('utf-8') line, self._buffer = self._buffer.split('\n', 1) return line def close(self): """关闭连接。""" if self.sock is not None: self.sock.close() self.sock = None print('连接已关闭') if __name__ == "__main__": client = MeterVisionClient() try: client.connect() for i in range(5): results = client.get_results() print(f"[{i + 1}] 收到:", results) time.sleep(1) except ConnectionRefusedError as e: print(e) except KeyboardInterrupt: print('连接已中断') finally: client.close() 发送命令示例(``examples/socket_commands_example.py``): .. code-block:: python import socket def send_commands(commands, host='localhost', port=65432): """ 发送命令列表并打印服务器回复。 Args: commands: 不含结尾 \\n 的命令字符串列表 host, port: MeterVision 服务器地址 """ client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: client_socket.connect((host, port)) print(f"已连接到服务器 {host}:{port}") except ConnectionRefusedError as e: print(e) return try: payload = ''.join(cmd.strip() + '\n' for cmd in commands if cmd.strip()) client_socket.sendall(payload.encode('utf-8')) buffer = '' replies_expected = sum(1 for cmd in commands if cmd.strip()) replies_got = 0 while replies_got < replies_expected: data = client_socket.recv(4096) if not data: break buffer += data.decode('utf-8') while '\n' in buffer: line, buffer = buffer.split('\n', 1) print("回复:", line) replies_got += 1 if replies_got >= replies_expected: break except KeyboardInterrupt: print('连接已中断') finally: client_socket.close() if __name__ == "__main__": # 示例:请求结果并为指针区域执行命令 send_commands([ 'get', 'fix_center 1', 'result_mode 1 scale', ]) .. note:: 上述示例也位于程序安装目录下的 ``examples/socket_results_example.py`` 与 ``examples/socket_commands_example.py``。