.. _external: External Programs ================= .. _METLAB: https://dipaul.ru/catalog/element/metlab/ The MeterVision program achieves maximum efficiency when used in combination with other programs capable of processing *machine vision recognition results*. For example, such a program can be `METLAB`_ or user-written software. For this purpose, the program can send *recognition results* to external programs in two ways (files in ``output`` and a socket server; enable the modes in :ref:`res`). It is also possible to control certain MeterVision functions from external programs in two ways: 1. Via the text file ``input/command.txt`` in the :ref:`catalog`. 2. Over a **socket** (if "Send results over socket" is enabled in :ref:`settings`) — using the same command syntax; each command on a separate line; the server replies with ``OK`` / ``ERROR: ...`` (or with results for the ``get`` command). You can send several commands at once in the file or over the socket: each command on a separate line. Commands are executed from the first line to the last. The following commands are available: 1. Close the MeterVision program .. code-block:: exit 2. Save a :ref:`screen` * To the default folder (*output/screenshots/* in :ref:`catalog`) with the default file name (creation date and time) .. code-block:: screenshot * To the default folder with a custom file name .. code-block:: screenshot my_name.png * To a specified folder with a custom file name .. code-block:: screenshot path/to/my/dir/my_name.png 3. Open a zone settings file .. code-block:: open path/to/my/dir/settings.mv 4. Fix the background (pointer methods only) .. code-block:: fix_background n Where `n` is the target zone number 5. Remove the background fixation (pointer methods only) .. code-block:: unfix_background n Where `n` is the target zone number 6. Fix the center (pointer methods only) .. code-block:: fix_center n Where `n` is the target zone number 7. Remove the center fixation (pointer methods only) .. code-block:: unfix_center n Where `n` is the target zone number 8. Forget the zone statistics (pointer methods only) .. code-block:: forget_stats n Where `n` is the target zone number 9. Forget the background (pointer methods only) .. code-block:: forget_background n Where `n` is the target zone number 10. Switch to another scale (pointer methods only) .. code-block:: scale n m Where `n` is the target zone number and `m` is the index of the scale to switch to 11. Switch the result display mode (pointer methods only) * Result is the scale reading .. code-block:: result_mode n scale * Result is the pointer inclination angle (in degrees) .. code-block:: result_mode n angle Where `n` is the target zone number Example input/command.txt file ------------------------------- .. code-block:: screenshot my_name.png unfix_background 1 fix_center 1 scale 1 3 result_mode 1 angle Sending commands and results over socket ---------------------------------------- When the socket mode is enabled (see :ref:`res`), an external program connects to the host/port from ``input/socket_config.txt`` and sends text lines ending with a newline. The command and reply separator is a newline. Several commands can be sent in one connection; the server replies with one line per command (for ``get`` — a results line like ``{1: '24.10', 2: 'AC'}``, for other commands — ``OK`` or ``ERROR: ...``). * ``get`` or ``results`` — get the current recognition results (one reply per request; the command can be repeated as many times as needed on the same connection). * Any command from the list above — perform the action; reply ``OK`` or ``ERROR: ...``. Example command sequence: .. code-block:: get fix_center 1 scale 1 2 Example of requesting results (``examples/socket_results_example.py``). You can call ``get_results()`` multiple times on the same connection: .. 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): """Connect to the MeterVision server.""" self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((self.host, self.port)) self._buffer = '' print(f"Connected to server {self.host}:{self.port}") def get_results(self): """ Request current results. Can be called repeatedly on the same connection. Returns a string like "{1: '24.10', 2: 'AC'}". """ if self.sock is None: raise RuntimeError('Call connect() first') self.sock.sendall(b'get\n') while '\n' not in self._buffer: chunk = self.sock.recv(4096) if not chunk: raise ConnectionError('Server closed the connection') self._buffer += chunk.decode('utf-8') line, self._buffer = self._buffer.split('\n', 1) return line def close(self): """Close the connection.""" if self.sock is not None: self.sock.close() self.sock = None print('Connection closed') if __name__ == "__main__": client = MeterVisionClient() try: client.connect() for i in range(5): results = client.get_results() print(f"[{i + 1}] Received:", results) time.sleep(1) except ConnectionRefusedError as e: print(e) except KeyboardInterrupt: print('Connection interrupted') finally: client.close() Example of sending commands (``examples/socket_commands_example.py``): .. code-block:: python import socket def send_commands(commands, host='localhost', port=65432): """ Sends a list of commands and prints server replies. Args: commands: list of command strings without a trailing \\n host, port: MeterVision server address """ client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: client_socket.connect((host, port)) print(f"Connected to server {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("Reply:", line) replies_got += 1 if replies_got >= replies_expected: break except KeyboardInterrupt: print('Connection interrupted') finally: client_socket.close() if __name__ == "__main__": # Example: request results and run commands for a pointer zone send_commands([ 'get', 'fix_center 1', 'result_mode 1 scale', ]) .. note:: These examples are also available as ``examples/socket_results_example.py`` and ``examples/socket_commands_example.py`` relative to the program installation directory.