text: """
  - This driver is responsible for providing an interface for the mag deck
  - The driver is the only system component that knows about the mag-deck's
    GCODES or how the mag-deck communicates
  
  - The driver is NOT responsible for interpreting deck states in any way
    or knowing anything about what the device is being used for
  """
  
  from __future__ import annotations
  
  import asyncio
  import logging
  from typing import Dict, Optional
  from enum import Enum
  
  from opentrons.drivers import utils
  from opentrons.drivers.command_builder import CommandBuilder
  from opentrons.drivers.asyncio.communication import SerialConnection
  from .abstract import AbstractMagDeckDriver
  
  log = logging.getLogger(__name__)
  
  DEFAULT_MAG_DECK_TIMEOUT = 10  # Quite large to account for probe time
  
  DEFAULT_COMMAND_RETRIES = 3
  
  
  class GCODE(str, Enum):
      HOME = "G28.2"
      PROBE_PLATE = "G38.2"
      GET_PLATE_HEIGHT = "M836"
      GET_CURRENT_POSITION = "M114.2"
      MOVE = "G0"
      DEVICE_INFO = "M115"
      PROGRAMMING_MODE = "dfu"
  
  
  MAG_DECK_BAUDRATE = 115200
  
  MAG_DECK_COMMAND_TERMINATOR = '\r\n\r\n'
  MAG_DECK_ACK = 'ok\r\nok\r\n'
  
  
  # Number of digits after the decimal point for millimeter values
  # being sent to/from magnetic module
  GCODE_ROUNDING_PRECISION = 3
  
  
  class MagDeckError(Exception):
      pass
  
  
  class MagDeckDriver(AbstractMagDeckDriver):
  
      @classmethod
      async def create(
              cls, port: str, loop: Optional[asyncio.AbstractEventLoop] = None
      ) -> MagDeckDriver:
          """
          Create a mag deck driver.
  
          Args:
              port: port or url of magdeck
              loop: optional event loop
  
          Returns: driver
          """
          connection = await SerialConnection.create(
              port=port, baud_rate=MAG_DECK_BAUDRATE,
              timeout=DEFAULT_MAG_DECK_TIMEOUT, ack=MAG_DECK_ACK,
              loop=loop
          )
          return cls(connection=connection)
  
      def __init__(self, connection: SerialConnection):
          """
          Constructor
  
          Args:
              connection: connection to magdeck.
          """
          self._connection = connection
  
      async def connect(self) -> None:
          """Connect to device"""
          await self._connection.open()
  
      async def disconnect(self) -> None:
          """Disconnect from device"""
          await self._connection.close()
  
      async def is_connected(self) -> bool:
          """Check if connected."""
          return await self._connection.is_open()
  
      async def home(self) -> None:
          """Homes the magnet"""
          c = CommandBuilder(
              terminator=MAG_DECK_COMMAND_TERMINATOR
          ).add_gcode(
              gcode=GCODE.HOME
          )
          await self._send_command(c)
  
      async def probe_plate(self) -> None:
          """
          Probes for the deck plate and calculates the plate distance
          from home.
          To be used for calibrating MagDeck
          """
          c = CommandBuilder(
              terminator=MAG_DECK_COMMAND_TERMINATOR
          ).add_gcode(
              gcode=GCODE.PROBE_PLATE
          )
          await self._send_command(c)
  
      async def get_plate_height(self) -> float:
          """
          Default plate_height for the device is 30;
          calculated as MAX_TRAVEL_DISTANCE(45mm) - 15mm
          """
          c = CommandBuilder(
              terminator=MAG_DECK_COMMAND_TERMINATOR
          ).add_gcode(
              gcode=GCODE.GET_PLATE_HEIGHT
          )
          response = await self._send_command(c)
          data = utils.parse_key_values(response)
          return utils.parse_number(str(data.get('height')),
                                    GCODE_ROUNDING_PRECISION)
  
      async def get_mag_position(self) -> float:
          """
          Default mag_position for the device is 0.0
          i.e. it boots with the current position as 0.0
          """
          c = CommandBuilder(
              terminator=MAG_DECK_COMMAND_TERMINATOR
          ).add_gcode(
              gcode=GCODE.GET_CURRENT_POSITION
          )
  
          response = await self._send_command(c)
          data = utils.parse_key_values(response)
          return utils.parse_number(str(data.get('Z')),
                                    GCODE_ROUNDING_PRECISION)
  
      async def move(self, position_mm: float) -> None:
          """
          Move the magnets along Z axis where the home position is 0.0;
          position_mm-> a point along Z. Does not self-check if the position
          is outside of the deck's linear range
          """
          c = CommandBuilder(
              terminator=MAG_DECK_COMMAND_TERMINATOR
          ).add_gcode(
              gcode=GCODE.MOVE
          ).add_float(
              prefix="Z", value=position_mm, precision=GCODE_ROUNDING_PRECISION
          )
          await self._send_command(c)
  
      async def get_device_info(self) -> Dict[str, str]:
          """
          Queries Mag-Deck for it's build version, model, and serial number
  
          Returns: dict
              Where keys are the strings 'version', 'model', and 'serial',
              and each value is a string identifier
  
              {
                  'serial': '1aa11bb22',
                  'model': '1aa11bb22',
                  'version': '1aa11bb22'
              }
  
          Example input from Mag-Deck's serial response:
              "serial:aa11bb22 model:aa11bb22 version:aa11bb22"
          """
          c = CommandBuilder(
              terminator=MAG_DECK_COMMAND_TERMINATOR
          ).add_gcode(
              gcode=GCODE.DEVICE_INFO
          )
          response = await self._send_command(c)
          return utils.parse_device_information(device_info_string=response)
  
      async def enter_programming_mode(self) -> None:
          """
          Enters and stays in DFU mode for 8 seconds.
          The module resets upon exiting the mode
          which causes the robot to lose serial connection to it.
          The connection can be restored by performing a .disconnect()
          followed by a .connect() to the same symlink node
          """
          c = CommandBuilder(
              terminator=MAG_DECK_COMMAND_TERMINATOR
          ).add_gcode(
              gcode=GCODE.PROGRAMMING_MODE
          )
          await self._send_command(c)
  
      async def _send_command(self, command: CommandBuilder) -> str:
          """
          Send the command
  
          Args:
              command: command to send
  
          Returns:
              command response
          """
          response = await self._connection.send_command(
              command=command,
              retries=DEFAULT_COMMAND_RETRIES
          )
          return response
  