{ "cells": [ { "cell_type": "markdown", "id": "overview", "metadata": {}, "source": [ "# Mettler Toledo MT-SICS scales\n", "\n", "`MTSICSDriver` connects to Mettler Toledo scales and weigh modules that implement the\n", "MT-SICS serial protocol. It discovers the commands advertised by the connected instrument\n", "during setup, so one driver can support multiple models with different command sets.\n", "\n", "| Property | Value |\n", "|---|---|\n", "| Protocol | MT-SICS (Mettler Toledo Standard Interface Command Set) |\n", "| Communication | RS-232 serial |\n", "| Driver default baud rate | 9600 |\n", "| Default USB adapter match | `0x0403:0x6001` (FTDI FT232R) |\n", "| PyLabRobot API | `MTSICSDriver` |\n", "| Hardware-validated example | [WXS205SDU/15](https://www.mt.com/us/en/home/products/Industrial_Weighing_Solutions/high-precision-weigh-sensors/weigh-module-wxs205sdu-15-11121008.html), reported by the instrument as WXS205SDU WXA-Bridge, firmware 1.10 |\n", "\n", "Support is based on the commands reported by the instrument, not on a hard-coded model name.\n", "The WXS205SDU/15 is the currently hardware-validated example; other MT-SICS models may expose a\n", "different subset of the methods shown below.\n" ] }, { "cell_type": "markdown", "id": "device-card", "metadata": {}, "source": [ "```{device-card} mettler-toledo-mt-sics\n", "```" ] }, { "cell_type": "markdown", "id": "communication", "metadata": {}, "source": [ "## How it communicates\n", "\n", "MT-SICS (Mettler Toledo Standard Interface Command Set) is an ASCII request/response\n", "protocol. Commands and responses are terminated by carriage return and line feed. The driver\n", "communicates through PyLabRobot's serial transport and handles framing, response parsing,\n", "multi-line responses, and MT-SICS error codes.\n", "\n", "During `setup()`, the driver resets the interface, queries `I0` to discover supported\n", "commands, reads the device identity and firmware, and selects grams as the host unit when the\n", "instrument supports that setting. A method whose MT-SICS command is unavailable on the\n", "connected model raises `MettlerToledoError` before sending it." ] }, { "cell_type": "markdown", "id": "physical-setup", "metadata": {}, "source": [ "## Physical setup\n", "\n", "Hardware layouts and connectors vary by model. Some systems use a separate load cell,\n", "electronic unit, and terminal, while others integrate these components. Follow the manual for\n", "your instrument, then connect its MT-SICS-capable RS-232 interface to the computer. A\n", "USB-to-serial adapter is normally required.\n", "\n", "The driver defaults to the FTDI FT232R VID:PID `0x0403:0x6001`. If your adapter uses different\n", "identifiers, pass the serial port explicitly or provide its `vid` and `pid` when creating the\n", "driver.\n", "\n", "Install PyLabRobot with serial support before continuing:\n", "\n", "```bash\n", "pip install \"pylabrobot[serial]\"\n", "```\n", "\n", "```{warning}\n", "Place the scale on a stable, level surface and follow the warm-up and environmental guidance\n", "for your model before measuring. An instrument that is not ready may report that a command is\n", "understood but not currently executable.\n", "```" ] }, { "cell_type": "markdown", "id": "connect", "metadata": {}, "source": [ "## Connect\n", "\n", "Create the driver with the serial port used by the scale and call `setup()`. Port names are\n", "typically `/dev/ttyUSB0` on Linux, `/dev/cu.usbserial-*` on macOS, and `COM3` or similar on\n", "Windows. If `port` is omitted, the driver searches for the configured USB VID and PID.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "connect-code", "metadata": {}, "outputs": [], "source": [ "from pylabrobot.mettler_toledo import MTSICSDriver\n", "\n", "scale = MTSICSDriver(port=\"/dev/cu.usbserial-110\") # replace with your serial port\n", "await scale.setup()" ] }, { "cell_type": "markdown", "id": "discovered-device", "metadata": {}, "source": [ "### Confirm the discovered instrument\n", "\n", "`setup()` records the identity and capacity reported by the instrument. Inspect these values\n", "before starting a measurement workflow, especially when several serial devices are connected.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "discovered-device-code", "metadata": {}, "outputs": [], "source": [ "print(f\"Model: {scale.device_type}\")\n", "print(f\"Serial number: {scale.serial_number}\")\n", "print(f\"Firmware: {scale.firmware_version}\")\n", "print(f\"Capacity: {scale.capacity} g\")" ] }, { "cell_type": "markdown", "id": "zero", "metadata": {}, "source": [ "## Zero the empty scale\n", "\n", "Remove everything from the weighing platform, then call `zero()`. The default waits for a\n", "stable reading. Use `zero(timeout=0)` only when an immediate zero is preferable to waiting for\n", "stability." ] }, { "cell_type": "code", "execution_count": null, "id": "zero-code", "metadata": {}, "outputs": [], "source": [ "await scale.zero()" ] }, { "cell_type": "markdown", "id": "tare", "metadata": {}, "source": [ "## Tare a container\n", "\n", "Place the empty container on the platform and wait for the reading to settle. `tare()` stores\n", "its weight so subsequent readings report only the sample's net weight." ] }, { "cell_type": "code", "execution_count": null, "id": "tare-code", "metadata": {}, "outputs": [], "source": [ "await scale.tare()" ] }, { "cell_type": "markdown", "id": "read-weight", "metadata": {}, "source": [ "## Read a stable weight\n", "\n", "Add the sample to the tared container. `read_weight()` waits for stability and returns the\n", "weight in grams as a `float`." ] }, { "cell_type": "code", "execution_count": null, "id": "read-weight-code", "metadata": {}, "outputs": [], "source": [ "weight_g = await scale.read_weight()\n", "print(f\"Weight: {weight_g:.4f} g\")" ] }, { "cell_type": "markdown", "id": "read-immediate", "metadata": {}, "source": [ "### Read immediately\n", "\n", "Use `timeout=0` when the current value is needed even if it is still changing. The result is\n", "still expressed in grams." ] }, { "cell_type": "code", "execution_count": null, "id": "read-immediate-code", "metadata": {}, "outputs": [], "source": [ "current_weight_g = await scale.read_weight(timeout=0)\n", "print(f\"Current weight: {current_weight_g:.4f} g\")" ] }, { "cell_type": "markdown", "id": "tare-value", "metadata": {}, "source": [ "### Inspect the stored tare\n", "\n", "Query the tare value currently stored by the scale." ] }, { "cell_type": "code", "execution_count": null, "id": "tare-value-code", "metadata": {}, "outputs": [], "source": [ "tare_weight_g = await scale.request_tare_weight()\n", "print(f\"Stored tare: {tare_weight_g:.4f} g\")" ] }, { "cell_type": "markdown", "id": "clear-tare", "metadata": {}, "source": [ "### Clear the tare\n", "\n", "Remove the container, then clear the stored tare when the workflow is finished." ] }, { "cell_type": "code", "execution_count": null, "id": "clear-tare-code", "metadata": {}, "outputs": [], "source": [ "await scale.clear_tare()" ] }, { "cell_type": "markdown", "id": "temperature", "metadata": {}, "source": [ "## Measure the internal temperature\n", "\n", "Some MT-SICS instruments, including the hardware-validated WXS205SDU/15, expose an internal\n", "temperature sensor with the `M28` command. This can be useful when temperature affects density\n", "calculations in gravimetric verification. Skip this call if your model does not advertise\n", "`M28`; the driver will otherwise raise `MettlerToledoError`.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "temperature-code", "metadata": {}, "outputs": [], "source": [ "temperature_c = await scale.measure_temperature()\n", "print(f\"Internal temperature: {temperature_c:.1f} °C\")" ] }, { "cell_type": "markdown", "id": "identity-queries", "metadata": {}, "source": [ "## Query device identity\n", "\n", "Identity methods can be called again after setup when a workflow needs to record instrument\n", "provenance alongside its measurements. The additional identity fields below are model\n", "dependent; an unsupported command raises `MettlerToledoError`.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "identity-queries-code", "metadata": {}, "outputs": [], "source": [ "identity = {\n", " \"serial_number\": await scale.request_serial_number(),\n", " \"device_type\": await scale.request_device_type(),\n", " \"model\": await scale.request_model_designation(),\n", " \"firmware\": await scale.request_firmware_version(),\n", " \"software_material_number\": await scale.request_software_material_number(),\n", "}\n", "identity" ] }, { "cell_type": "markdown", "id": "device-status", "metadata": {}, "source": [ "## Query device status\n", "\n", "The following read-only calls report the instrument's clock, uptime, and next configured\n", "service date when their corresponding commands are supported by the connected model.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "device-status-code", "metadata": {}, "outputs": [], "source": [ "status = {\n", " \"date\": await scale.request_date(),\n", " \"time\": await scale.request_time(),\n", " \"uptime_minutes\": await scale.request_uptime_minutes(),\n", " \"next_service_date\": await scale.request_next_service_date(),\n", "}\n", "status" ] }, { "cell_type": "markdown", "id": "weight-status", "metadata": {}, "source": [ "## Read weight with MT-SICS status\n", "\n", "`request_net_weight_with_status()` exposes the structured MT-SICS response when a workflow\n", "needs the stability state, unit code, readability, approval state, or tare information in\n", "addition to the numeric weight." ] }, { "cell_type": "code", "execution_count": null, "id": "weight-status-code", "metadata": {}, "outputs": [], "source": [ "response = await scale.request_net_weight_with_status()\n", "print(f\"Command: {response.command}\")\n", "print(f\"Status: {response.status}\")\n", "print(f\"Data: {response.data}\")" ] }, { "cell_type": "markdown", "id": "detailed-info", "metadata": {}, "source": [ "## Read a multi-line response\n", "\n", "Some MT-SICS commands return several lines. The driver returns them as a list of\n", "`MettlerToledoResponse` objects. For example, instruments that implement `I14` can report\n", "installed components through device-information category 0.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "detailed-info-code", "metadata": {}, "outputs": [], "source": [ "device_info = await scale.request_device_info(category=0)\n", "for line in device_info:\n", " print(line.command, line.status, line.data)" ] }, { "cell_type": "markdown", "id": "configuration", "metadata": {}, "source": [ "## Inspect weighing configuration\n", "\n", "These configuration-query methods are read-only and model dependent. Their returned values are\n", "MT-SICS setting codes; consult the Mettler Toledo MT-SICS reference for the meaning of each code\n", "on your model.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "configuration-code", "metadata": {}, "outputs": [], "source": [ "configuration = {\n", " \"weighing_mode\": await scale.request_weighing_mode(),\n", " \"environment_condition\": await scale.request_environment_condition(),\n", " \"auto_zero\": await scale.request_auto_zero(),\n", " \"update_rate_hz\": await scale.request_update_rate(),\n", "}\n", "configuration" ] }, { "cell_type": "markdown", "id": "model-differences", "metadata": {}, "source": [ "## Command availability across models\n", "\n", "Not every MT-SICS instrument implements every command. During `setup()`, the driver queries\n", "`I0` and records the commands advertised by the connected instrument. Calling a method whose\n", "command was not advertised raises `MettlerToledoError` before anything is sent.\n", "\n", "For example, the WXS205SDU/15 WXA-Bridge used for hardware validation does not expose the timed\n", "zero/tare/read commands, display commands, the cancel-all command, or remaining-range query.\n", "Those methods remain available for other MT-SICS models that advertise the corresponding\n", "commands.\n", "\n", "Methods such as `set_device_id()`, `set_date()`, and `set_time()` intentionally change\n", "persistent device state and are therefore not run in this hello-world guide.\n" ] }, { "cell_type": "markdown", "id": "disconnect", "metadata": {}, "source": [ "## Disconnect\n", "\n", "Always stop the driver when the workflow finishes. `stop()` attempts to reset the interface\n", "to a determined state before closing the serial connection." ] }, { "cell_type": "code", "execution_count": null, "id": "disconnect-code", "metadata": {}, "outputs": [], "source": [ "await scale.stop()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.11.0" } }, "nbformat": 4, "nbformat_minor": 5 }