diff --git a/.github/actions/python/setup/action.yaml b/.github/actions/python/setup/action.yaml index 5e728acb9c9..c90563ccd1f 100644 --- a/.github/actions/python/setup/action.yaml +++ b/.github/actions/python/setup/action.yaml @@ -14,6 +14,8 @@ runs: - shell: bash run: | if [[ "${OSTYPE}" =~ "linux" ]]; then + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update sudo apt-get install -y --no-install-recommends libsystemd-dev fi diff --git a/.github/workflows/app-test-build-deploy.yaml b/.github/workflows/app-test-build-deploy.yaml index 738fa369e58..878a875bdfc 100644 --- a/.github/workflows/app-test-build-deploy.yaml +++ b/.github/workflows/app-test-build-deploy.yaml @@ -61,7 +61,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 @@ -116,7 +119,10 @@ jobs: run: make --version - name: 'install libudev and libsystemd' if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 @@ -247,7 +253,10 @@ jobs: run: make --version - name: 'install libudev and libsystemd' if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 @@ -423,7 +432,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 diff --git a/.github/workflows/components-test-build-deploy.yaml b/.github/workflows/components-test-build-deploy.yaml index 6b39fb3b1c8..7d4f2f5f49a 100644 --- a/.github/workflows/components-test-build-deploy.yaml +++ b/.github/workflows/components-test-build-deploy.yaml @@ -44,7 +44,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: @@ -77,7 +80,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: @@ -175,7 +181,10 @@ jobs: node-version: '18.19.0' registry-url: 'https://registry.npmjs.org' - name: 'install udev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'setup-js' run: | npm config set cache ./.npm-cache diff --git a/.github/workflows/g-code-testing-lint-test.yaml b/.github/workflows/g-code-testing-lint-test.yaml index 89fe00f4d2d..e174bc7ac52 100644 --- a/.github/workflows/g-code-testing-lint-test.yaml +++ b/.github/workflows/g-code-testing-lint-test.yaml @@ -46,7 +46,10 @@ jobs: with: fetch-depth: 0 - name: 'install udev' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - uses: 'actions/setup-node@v3' with: node-version: '18.19.0' diff --git a/.github/workflows/js-check.yaml b/.github/workflows/js-check.yaml index b880cb33d48..8a02c1823ba 100644 --- a/.github/workflows/js-check.yaml +++ b/.github/workflows/js-check.yaml @@ -54,7 +54,10 @@ jobs: const { buildComplexEnvVars } = require(`${process.env.GITHUB_WORKSPACE}/.github/workflows/utils.js`) buildComplexEnvVars(core, context) - name: 'install libudev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: diff --git a/.github/workflows/ll-test-build-deploy.yaml b/.github/workflows/ll-test-build-deploy.yaml index d25cfaab3aa..140537593e2 100644 --- a/.github/workflows/ll-test-build-deploy.yaml +++ b/.github/workflows/ll-test-build-deploy.yaml @@ -53,7 +53,10 @@ jobs: git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - name: 'install libudev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: @@ -93,7 +96,10 @@ jobs: with: node-version: '18.19.0' - name: 'install libudev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: @@ -133,7 +139,10 @@ jobs: with: node-version: '18.19.0' - name: 'install libudev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: @@ -176,7 +185,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 diff --git a/.github/workflows/opentrons-ai-client-test-build-deploy.yaml b/.github/workflows/opentrons-ai-client-test-build-deploy.yaml index 072366ab0d7..2f569d9bf78 100644 --- a/.github/workflows/opentrons-ai-client-test-build-deploy.yaml +++ b/.github/workflows/opentrons-ai-client-test-build-deploy.yaml @@ -48,7 +48,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 diff --git a/.github/workflows/pd-test-build-deploy.yaml b/.github/workflows/pd-test-build-deploy.yaml index f2af41620be..9f23419da94 100644 --- a/.github/workflows/pd-test-build-deploy.yaml +++ b/.github/workflows/pd-test-build-deploy.yaml @@ -53,7 +53,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v2 with: @@ -99,7 +102,10 @@ jobs: node-version: '18.19.0' - name: 'install udev for usb-detection' if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: @@ -135,7 +141,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: @@ -176,7 +185,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 diff --git a/.github/workflows/react-api-client-test.yaml b/.github/workflows/react-api-client-test.yaml index af8e4015497..a8f5ed959b2 100644 --- a/.github/workflows/react-api-client-test.yaml +++ b/.github/workflows/react-api-client-test.yaml @@ -41,7 +41,10 @@ jobs: with: node-version: '18.19.0' - name: 'install libudev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: diff --git a/.github/workflows/shared-data-test-lint-deploy.yaml b/.github/workflows/shared-data-test-lint-deploy.yaml index 57653337132..56dcf76f00a 100644 --- a/.github/workflows/shared-data-test-lint-deploy.yaml +++ b/.github/workflows/shared-data-test-lint-deploy.yaml @@ -80,7 +80,10 @@ jobs: fetch-depth: 0 - name: 'install udev for usb-detection' if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - uses: 'actions/setup-node@v1' with: node-version: '18.19.0' @@ -117,7 +120,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: @@ -159,7 +165,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - uses: 'actions/setup-python@v4' with: python-version: '3.10' diff --git a/.github/workflows/step-generation-test.yaml b/.github/workflows/step-generation-test.yaml index a0a9f7fef09..7ac65f3997e 100644 --- a/.github/workflows/step-generation-test.yaml +++ b/.github/workflows/step-generation-test.yaml @@ -40,7 +40,10 @@ jobs: with: node-version: '18.19.0' - name: 'install udev for usb-detection' - run: sudo apt-get update && sudo apt-get install libudev-dev + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev - name: 'cache yarn cache' uses: actions/cache@v3 with: diff --git a/api/release-notes-internal.md b/api/release-notes-internal.md index 261d55e2100..353df2e8833 100644 --- a/api/release-notes-internal.md +++ b/api/release-notes-internal.md @@ -2,6 +2,22 @@ For more details about this release, please see the full [technical change log][ [technical change log]: https://github.com/Opentrons/opentrons/releases +## Internal Release 1.5.0-alpha.1 + +This internal release is from the `edge` branch to contain rapid dev on new features for 7.3.0. This release is for internal testing purposes and if used may require a factory reset of the robot to return to a stable version. + + + +--- + +## Internal Release 1.5.0-alpha.0 + +This internal release is from the `edge` branch to contain rapid dev on new features for 7.3.0. This release is for internal testing purposes and if used may require a factory reset of the robot to return to a stable version. + + + +--- + ## Internal Release 1.4.0-alpha.1 This internal release is from the `edge` branch to contain rapid dev on new features for 7.3.0. This release is for internal testing purposes and if used may require a factory reset of the robot to return to a stable version. diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index ea0b610f8b4..9a22a3e2e13 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -1524,10 +1524,10 @@ async def teardown_tip_detector(self, mount: OT3Mount) -> None: async def get_tip_status( self, mount: OT3Mount, - ht_operational_sensor: Optional[InstrumentProbeType] = None, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> TipStateType: return await self.tip_presence_manager.get_tip_status( - mount, ht_operational_sensor + mount, follow_singular_sensor ) def current_tip_state(self, mount: OT3Mount) -> Optional[bool]: diff --git a/api/src/opentrons/hardware_control/backends/ot3simulator.py b/api/src/opentrons/hardware_control/backends/ot3simulator.py index 26d6237e9a3..e0c8fe1bc89 100644 --- a/api/src/opentrons/hardware_control/backends/ot3simulator.py +++ b/api/src/opentrons/hardware_control/backends/ot3simulator.py @@ -783,7 +783,7 @@ def subsystems(self) -> Dict[SubSystem, SubSystemState]: async def get_tip_status( self, mount: OT3Mount, - ht_operational_sensor: Optional[InstrumentProbeType] = None, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> TipStateType: return TipStateType(self._sim_tip_state[mount]) diff --git a/api/src/opentrons/hardware_control/backends/tip_presence_manager.py b/api/src/opentrons/hardware_control/backends/tip_presence_manager.py index 0e46d713955..f2401d23f69 100644 --- a/api/src/opentrons/hardware_control/backends/tip_presence_manager.py +++ b/api/src/opentrons/hardware_control/backends/tip_presence_manager.py @@ -116,21 +116,21 @@ def current_tip_state(self, mount: OT3Mount) -> Optional[bool]: @staticmethod def _get_tip_presence( results: List[tip_types.TipNotification], - ht_operational_sensor: Optional[InstrumentProbeType] = None, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> TipStateType: """ - We can use ht_operational_sensor used to specify that we only care + We can use follow_singular_sensor used to specify that we only care about the status of one tip presence sensor on a high throughput pipette, and the other is allowed to be different. """ - if ht_operational_sensor: - target_sensor_id = sensor_id_for_instrument(ht_operational_sensor) + if follow_singular_sensor: + target_sensor_id = sensor_id_for_instrument(follow_singular_sensor) for r in results: if r.sensor == target_sensor_id: return TipStateType(r.presence) # raise an error if requested sensor response isn't found raise GeneralError( - message=f"Requested status for sensor {ht_operational_sensor} not found." + message=f"Requested status for sensor {follow_singular_sensor} not found." ) # more than one sensor reported, we have to check if their states match if len(set(r.presence for r in results)) > 1: @@ -142,11 +142,11 @@ def _get_tip_presence( async def get_tip_status( self, mount: OT3Mount, - ht_operational_sensor: Optional[InstrumentProbeType] = None, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> TipStateType: detector = self.get_detector(mount) return self._get_tip_presence( - await detector.request_tip_status(), ht_operational_sensor + await detector.request_tip_status(), follow_singular_sensor ) def get_detector(self, mount: OT3Mount) -> TipDetector: diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index dbc76181f24..21c3f70dab7 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -2072,7 +2072,7 @@ async def _high_throughput_check_tip(self) -> AsyncIterator[None]: async def get_tip_presence_status( self, mount: Union[top_types.Mount, OT3Mount], - ht_operational_sensor: Optional[InstrumentProbeType] = None, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> TipStateType: """ Check tip presence status. If a high throughput pipette is present, @@ -2087,7 +2087,7 @@ async def get_tip_presence_status( ): await stack.enter_async_context(self._high_throughput_check_tip()) result = await self._backend.get_tip_status( - real_mount, ht_operational_sensor + real_mount, follow_singular_sensor ) return result @@ -2095,10 +2095,10 @@ async def verify_tip_presence( self, mount: Union[top_types.Mount, OT3Mount], expected: TipStateType, - ht_operational_sensor: Optional[InstrumentProbeType] = None, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> None: real_mount = OT3Mount.from_mount(mount) - status = await self.get_tip_presence_status(real_mount, ht_operational_sensor) + status = await self.get_tip_presence_status(real_mount, follow_singular_sensor) if status != expected: raise FailedTipStateCheck(expected, status.value) diff --git a/api/src/opentrons/hardware_control/protocols/flex_instrument_configurer.py b/api/src/opentrons/hardware_control/protocols/flex_instrument_configurer.py index 0606b8847f4..9b156f0dffa 100644 --- a/api/src/opentrons/hardware_control/protocols/flex_instrument_configurer.py +++ b/api/src/opentrons/hardware_control/protocols/flex_instrument_configurer.py @@ -1,5 +1,5 @@ """Flex-specific extensions to instrument configuration.""" -from typing import Union +from typing import Union, Optional from typing_extensions import Protocol from .types import MountArgType @@ -9,6 +9,7 @@ ) from opentrons.hardware_control.types import ( TipStateType, + InstrumentProbeType, ) from opentrons.hardware_control.instruments.ot3.instrument_calibration import ( PipetteOffsetSummary, @@ -42,7 +43,10 @@ async def get_tip_presence_status( ... async def verify_tip_presence( - self, mount: MountArgType, expected: TipStateType + self, + mount: MountArgType, + expected: TipStateType, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> None: """Check tip presence status and raise if it does not match `expected`.""" ... diff --git a/api/src/opentrons/protocol_engine/commands/verify_tip_presence.py b/api/src/opentrons/protocol_engine/commands/verify_tip_presence.py index 1d56c8e66bf..67aa5d1dc34 100644 --- a/api/src/opentrons/protocol_engine/commands/verify_tip_presence.py +++ b/api/src/opentrons/protocol_engine/commands/verify_tip_presence.py @@ -8,7 +8,7 @@ from .pipetting_common import PipetteIdMixin from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate -from ..types import TipPresenceStatus +from ..types import TipPresenceStatus, InstrumentSensorId if TYPE_CHECKING: from ..execution import TipHandler @@ -23,6 +23,9 @@ class VerifyTipPresenceParams(PipetteIdMixin): expectedState: TipPresenceStatus = Field( ..., description="The expected tip presence status on the pipette." ) + followSingularSensor: Optional[InstrumentSensorId] = Field( + default=None, description="The sensor id to follow if the other can be ignored." + ) class VerifyTipPresenceResult(BaseModel): @@ -47,10 +50,16 @@ async def execute(self, params: VerifyTipPresenceParams) -> VerifyTipPresenceRes """Verify if tip presence is as expected for the requested pipette.""" pipette_id = params.pipetteId expected_state = params.expectedState + follow_singular_sensor = ( + InstrumentSensorId.to_instrument_probe_type(params.followSingularSensor) + if params.followSingularSensor + else None + ) await self._tip_handler.verify_tip_presence( pipette_id=pipette_id, expected=expected_state, + follow_singular_sensor=follow_singular_sensor, ) return VerifyTipPresenceResult() diff --git a/api/src/opentrons/protocol_engine/execution/tip_handler.py b/api/src/opentrons/protocol_engine/execution/tip_handler.py index 51cf4708377..e43685d2ebb 100644 --- a/api/src/opentrons/protocol_engine/execution/tip_handler.py +++ b/api/src/opentrons/protocol_engine/execution/tip_handler.py @@ -3,7 +3,7 @@ from typing_extensions import Protocol as TypingProtocol from opentrons.hardware_control import HardwareControlAPI -from opentrons.hardware_control.types import FailedTipStateCheck +from opentrons.hardware_control.types import FailedTipStateCheck, InstrumentProbeType from opentrons_shared_data.errors.exceptions import ( CommandPreconditionViolated, CommandParameterLimitViolated, @@ -74,7 +74,10 @@ async def get_tip_presence(self, pipette_id: str) -> TipPresenceStatus: """Get tip presence status on the pipette.""" async def verify_tip_presence( - self, pipette_id: str, expected: TipPresenceStatus + self, + pipette_id: str, + expected: TipPresenceStatus, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> None: """Verify the expected tip presence status.""" @@ -237,7 +240,10 @@ async def get_tip_presence(self, pipette_id: str) -> TipPresenceStatus: return TipPresenceStatus.UNKNOWN async def verify_tip_presence( - self, pipette_id: str, expected: TipPresenceStatus + self, + pipette_id: str, + expected: TipPresenceStatus, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> None: """Verify the expecterd tip presence status of the pipette. @@ -247,7 +253,9 @@ async def verify_tip_presence( try: ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api) hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() - await ot3api.verify_tip_presence(hw_mount, expected.to_hw_state()) + await ot3api.verify_tip_presence( + hw_mount, expected.to_hw_state(), follow_singular_sensor + ) except HardwareNotSupportedError: # Tip presence sensing is not supported on the OT2 pass @@ -332,7 +340,10 @@ async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None: assert False, "TipHandler.add_tip should not be used with virtual pipettes" async def verify_tip_presence( - self, pipette_id: str, expected: TipPresenceStatus + self, + pipette_id: str, + expected: TipPresenceStatus, + follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> None: """Verify tip presence. diff --git a/api/src/opentrons/protocol_engine/types.py b/api/src/opentrons/protocol_engine/types.py index d7b0e981b2a..13e9515e447 100644 --- a/api/src/opentrons/protocol_engine/types.py +++ b/api/src/opentrons/protocol_engine/types.py @@ -10,7 +10,10 @@ from opentrons_shared_data.pipette.dev_types import PipetteNameType from opentrons.types import MountType, DeckSlotName, StagingSlotName -from opentrons.hardware_control.types import TipStateType as HwTipStateType +from opentrons.hardware_control.types import ( + TipStateType as HwTipStateType, + InstrumentProbeType, +) from opentrons.hardware_control.modules import ( ModuleType as ModuleType, ) @@ -830,6 +833,22 @@ class QuadrantNozzleLayoutConfiguration(BaseModel): ] # cutout_id, cutout_fixture_id, opentrons_module_serial_number +class InstrumentSensorId(str, Enum): + """Primary and secondary sensor ids.""" + + PRIMARY = "primary" + SECONDARY = "secondary" + BOTH = "both" + + def to_instrument_probe_type(self) -> InstrumentProbeType: + """Convert to InstrumentProbeType.""" + return { + InstrumentSensorId.PRIMARY: InstrumentProbeType.PRIMARY, + InstrumentSensorId.SECONDARY: InstrumentProbeType.SECONDARY, + InstrumentSensorId.BOTH: InstrumentProbeType.BOTH, + }[self] + + class TipPresenceStatus(str, Enum): """Tip presence status reported by a pipette.""" diff --git a/api/src/opentrons/util/performance_helpers.py b/api/src/opentrons/util/performance_helpers.py index ddd547e2ce7..a157908303d 100644 --- a/api/src/opentrons/util/performance_helpers.py +++ b/api/src/opentrons/util/performance_helpers.py @@ -20,23 +20,6 @@ ) -def _handle_package_import() -> Type[SupportsTracking]: - """Handle the import of the performance_metrics package. - - If the package is not available, return a stubbed tracker. - """ - try: - from performance_metrics import RobotContextTracker - - return RobotContextTracker - except ImportError: - return StubbedTracker - - -package_to_use = _handle_package_import() -_robot_context_tracker: SupportsTracking | None = None - - class StubbedTracker(SupportsTracking): """A stubbed tracker that does nothing.""" @@ -58,6 +41,23 @@ def store(self) -> None: pass +def _handle_package_import() -> Type[SupportsTracking]: + """Handle the import of the performance_metrics package. + + If the package is not available, return a stubbed tracker. + """ + try: + from performance_metrics import RobotContextTracker + + return RobotContextTracker + except ImportError: + return StubbedTracker + + +package_to_use = _handle_package_import() +_robot_context_tracker: SupportsTracking | None = None + + def _get_robot_context_tracker() -> SupportsTracking: """Singleton for the robot context tracker.""" global _robot_context_tracker diff --git a/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py b/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py index 6a84810ff61..e7e0284debe 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py @@ -413,11 +413,11 @@ async def test_verify_tip_presence_on_ot3( decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return( MountType.LEFT ) - await subject.verify_tip_presence("pipette-id", expected) + await subject.verify_tip_presence("pipette-id", expected, None) decoy.verify( await ot3_hardware_api.verify_tip_presence( - Mount.LEFT, expected.to_hw_state() + Mount.LEFT, expected.to_hw_state(), None ) ) diff --git a/app-shell/build/release-notes-internal.md b/app-shell/build/release-notes-internal.md index 591aa411a3c..e6925397157 100644 --- a/app-shell/build/release-notes-internal.md +++ b/app-shell/build/release-notes-internal.md @@ -1,6 +1,22 @@ For more details about this release, please see the full [technical changelog][]. [technical change log]: https://github.com/Opentrons/opentrons/releases +## Internal Release 1.5.0-alpha.1 + +This internal release is from the `edge` branch to contain rapid dev on new features for 7.3.0. This release is for internal testing purposes and if used may require a factory reset of the robot to return to a stable version. + + + +--- + +## Internal Release 1.5.0-alpha.0 + +This internal release is from the `edge` branch to contain rapid dev on new features for 7.3.0. This release is for internal testing purposes and if used may require a factory reset of the robot to return to a stable version. + + + +--- + ## Internal Release 1.4.0-alpha.1 This internal release is from the `edge` branch to contain rapid dev on new features for 7.3.0. This release is for internal testing purposes and if used may require a factory reset of the robot to return to a stable version. diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json index 74fbf93d3c2..360fbd2cc4e 100644 --- a/app/src/assets/localization/en/protocol_setup.json +++ b/app/src/assets/localization/en/protocol_setup.json @@ -3,10 +3,11 @@ "action_needed": "Action needed", "adapter_slot_location_module": "Slot {{slotName}}, {{adapterName}} on {{moduleName}}", "adapter_slot_location": "Slot {{slotName}}, {{adapterName}}", - "add_fixture_to_deck": "Add this fixture to your deck configuration. It will be referenced during protocol analysis.", "add_fixture": "Add {{fixtureName}} to deck configuration", "additional_labware": "{{count}} additional labware", "additional_off_deck_labware": "Additional Off-Deck Labware", + "add_this_deck_hardware": "Add this deck hardware to your deck configuration. It will be referenced during protocol analysis.", + "add_to_slot": "Add to slot {{slotName}}", "attach_gripper_failure_reason": "Attach the required gripper to continue", "attach_gripper": "attach gripper", "attach_module": "Attach module before calibrating", @@ -38,6 +39,7 @@ "calibration_status": "calibration status", "calibration": "Calibration", "cancel_and_restart_to_edit": "Cancel the run and restart setup to edit", + "cancel_protocol_and_edit_deck_config": "Cancel protocol and edit deck configuration", "choose_enum": "Choose {{displayName}}", "closing": "Closing...", "complete_setup_before_proceeding": "complete setup before continuing run", @@ -142,7 +144,6 @@ "module_setup_step_title": "Modules", "module_slot_location": "Slot {{slotName}}, {{moduleName}}", "module": "Module", - "modules_and_deck": "Modules & deck", "modules_connected_plural": "{{count}} modules attached", "modules_connected": "{{count}} module attached", "modules_setup_step_title": "Module Setup", @@ -249,12 +250,14 @@ "slot_number": "Slot Number", "status": "Status", "step": "STEP {{index}}", + "there_are_no_unconfigured_modules": "There are no un-configured {{module}} connected to the robot. Plug one in or remove an existing {{module}}, move it to the right place, and update the deck configuration.", "tip_length_cal_description_bullet": "Perform Tip Length Calibration for each new tip type used on a pipette.", "tip_length_cal_description": "This measures the Z distance between the bottom of the tip and the pipetteā€™s nozzle. If you redo the tip length calibration for the tip you used to calibrate a pipette, you will also have to redo that Pipette Offset Calibration.", "tip_length_cal_title": "Tip Length Calibration", "tip_length_calibration": "tip length calibration", "total_vol": "total volume", "update_deck": "Update deck", + "update_deck_config": "Update deck configuration", "updated": "Updated", "usb_connected_no_port_info": "USB Port Connected", "usb_port_connected": "USB Port {{port}}", diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx index 99f2328b1e4..91fb38c4cf2 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx @@ -59,8 +59,6 @@ import type { import type { ModalHeaderBaseProps } from '../../molecules/Modal/types' import type { LegacyModalProps } from '../../molecules/LegacyModal' -// type CutoutContents = Omit - interface AddFixtureModalProps { cutoutId: CutoutId setShowAddFixtureModal: (showAddFixtureModal: boolean) => void diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx index 0505cf0c921..533f134590d 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx @@ -17,8 +17,6 @@ import { } from '@opentrons/shared-data' import { getLabwareSetupItemGroups } from '../../../../pages/Protocols/utils' -import { getAttachedProtocolModuleMatches } from '../../../ProtocolSetupModulesAndDeck/utils' -import { useAttachedModules } from '../../hooks' import { LabwareInfoOverlay } from '../LabwareInfoOverlay' import { getLabwareRenderInfo } from '../utils/getLabwareRenderInfo' import { getProtocolModulesInfo } from '../utils/getProtocolModulesInfo' @@ -30,8 +28,6 @@ import type { ProtocolAnalysisOutput, } from '@opentrons/shared-data' -const ATTACHED_MODULE_POLL_MS = 5000 - interface SetupLabwareMapProps { runId: string protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null @@ -41,11 +37,6 @@ export function SetupLabwareMap({ runId, protocolAnalysis, }: SetupLabwareMapProps): JSX.Element | null { - const attachedModules = - useAttachedModules({ - refetchInterval: ATTACHED_MODULE_POLL_MS, - }) ?? [] - // early return null if no protocol analysis if (protocolAnalysis == null) return null @@ -56,16 +47,11 @@ export function SetupLabwareMap({ const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef) - const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( - attachedModules, - protocolModulesInfo - ) - const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter( commands ) - const modulesOnDeck = attachedProtocolModuleMatches.map(module => { + const modulesOnDeck = protocolModulesInfo.map(module => { const labwareInAdapterInMod = module.nestedLabwareId != null ? initialLoadedLabwareByAdapter[module.nestedLabwareId] diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx index 0519b557065..352bcf021e8 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx @@ -21,13 +21,11 @@ import { THERMOCYCLER_MODULE_V1, } from '@opentrons/shared-data' -import { useAttachedModules } from '../../hooks' import { LabwareInfoOverlay } from '../LabwareInfoOverlay' import { LiquidsLabwareDetailsModal } from './LiquidsLabwareDetailsModal' import { getWellFillFromLabwareId } from './utils' import { getLabwareRenderInfo } from '../utils/getLabwareRenderInfo' import { getStandardDeckViewLayerBlockList } from '../utils/getStandardDeckViewLayerBlockList' -import { getAttachedProtocolModuleMatches } from '../../../ProtocolSetupModulesAndDeck/utils' import { getProtocolModulesInfo } from '../utils/getProtocolModulesInfo' import type { @@ -35,8 +33,6 @@ import type { ProtocolAnalysisOutput, } from '@opentrons/shared-data' -const ATTACHED_MODULE_POLL_MS = 5000 - interface SetupLiquidsMapProps { runId: string protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null @@ -50,10 +46,6 @@ export function SetupLiquidsMap( const [liquidDetailsLabwareId, setLiquidDetailsLabwareId] = React.useState< string | null >(null) - const attachedModules = - useAttachedModules({ - refetchInterval: ATTACHED_MODULE_POLL_MS, - }) ?? [] if (protocolAnalysis == null) return null @@ -75,12 +67,7 @@ export function SetupLiquidsMap( const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef) - const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( - attachedModules, - protocolModulesInfo - ) - - const modulesOnDeck = attachedProtocolModuleMatches.map(module => { + const modulesOnDeck = protocolModulesInfo.map(module => { const labwareInAdapterInMod = module.nestedLabwareId != null ? initialLoadedLabwareByAdapter[module.nestedLabwareId] diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx index 81e5a005143..fa9e45852b5 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx @@ -212,7 +212,8 @@ describe('SetupLiquidsMap', () => { when(vi.mocked(getAttachedProtocolModuleMatches)) .calledWith( mockFetchModulesSuccessActionPayloadModules, - mockProtocolModuleInfo + mockProtocolModuleInfo, + [] ) .thenReturn([ { @@ -299,7 +300,8 @@ describe('SetupLiquidsMap', () => { when(vi.mocked(getAttachedProtocolModuleMatches)) .calledWith( mockFetchModulesSuccessActionPayloadModules, - mockProtocolModuleInfo + mockProtocolModuleInfo, + [] ) .thenReturn([ { diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/ChooseModuleToConfigureModal.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/ChooseModuleToConfigureModal.tsx index 59bc0b6e52e..6a6264b80c7 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/ChooseModuleToConfigureModal.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/ChooseModuleToConfigureModal.tsx @@ -1,17 +1,17 @@ import * as React from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' +import { useHistory } from 'react-router-dom' import { useDeckConfigurationQuery, useModulesQuery, } from '@opentrons/react-api-client' import { ALIGN_CENTER, - COLORS, DIRECTION_COLUMN, DIRECTION_ROW, Flex, - Icon, + PrimaryButton, SPACING, StyledText, TYPOGRAPHY, @@ -20,14 +20,19 @@ import { getFixtureDisplayName, getCutoutFixturesForModuleModel, MAGNETIC_BLOCK_V1, + getModuleDisplayName, } from '@opentrons/shared-data' import { getTopPortalEl } from '../../../../App/portal' import { LegacyModal } from '../../../../molecules/LegacyModal' import { Modal } from '../../../../molecules/Modal' +import { FixtureOption } from '../../../DeviceDetailsDeckConfiguration/AddFixtureModal' + +import { SmallButton } from '../../../../atoms/buttons' +import { useCloseCurrentRun } from '../../../ProtocolUpload/hooks' import type { ModuleModel, DeckDefinition } from '@opentrons/shared-data' -import { FixtureOption } from '../../../DeviceDetailsDeckConfiguration/AddFixtureModal' +const EQUIPMENT_POLL_MS = 5000 interface ModuleFixtureOption { moduleModel: ModuleModel usbPort?: number @@ -39,6 +44,8 @@ interface ChooseModuleToConfigureModalProps { deckDef: DeckDefinition isOnDevice: boolean requiredModuleModel: ModuleModel + robotName: string + displaySlotName: string } export const ChooseModuleToConfigureModal = ( @@ -50,9 +57,14 @@ export const ChooseModuleToConfigureModal = ( deckDef, requiredModuleModel, isOnDevice, + robotName, + displaySlotName, } = props const { t } = useTranslation(['protocol_setup', 'shared']) - const attachedModules = useModulesQuery().data?.data ?? [] + const history = useHistory() + const { closeCurrentRun } = useCloseCurrentRun() + const attachedModules = + useModulesQuery({ refetchInterval: EQUIPMENT_POLL_MS })?.data?.data ?? [] const deckConfig = useDeckConfigurationQuery()?.data ?? [] const unconfiguredModuleMatches = attachedModules.filter( @@ -94,17 +106,52 @@ export const ChooseModuleToConfigureModal = ( ) } ) + const handleCancelRun = (): void => { + closeCurrentRun() + } + const handleNavigateToDeviceDetails = (): void => { + history.push(`/devices/${robotName}`) + } + const emptyState = ( + + + {t('there_are_no_unconfigured_modules', { + module: getModuleDisplayName(requiredModuleModel), + })} + + {isOnDevice ? ( + + ) : ( + + {t('update_deck_config')} + + )} + + ) + + const contents = + fixtureOptions.length > 0 ? ( + + {t('add_this_deck_hardware')} + + {fixtureOptions} + + + ) : ( + emptyState + ) return createPortal( isOnDevice ? ( @@ -114,7 +161,7 @@ export const ChooseModuleToConfigureModal = ( paddingTop={SPACING.spacing8} gridGap={SPACING.spacing8} > - {fixtureOptions} + {contents} @@ -127,9 +174,8 @@ export const ChooseModuleToConfigureModal = ( gridGap={SPACING.spacing10} alignItems={ALIGN_CENTER} > - - {t('deck_conflict')} + {t('add_to_slot', { slotName: displaySlotName })} } @@ -143,7 +189,7 @@ export const ChooseModuleToConfigureModal = ( paddingTop={SPACING.spacing8} gridGap={SPACING.spacing8} > - {fixtureOptions} + {contents} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal.tsx index c696b4ecbdf..1783bd31754 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal.tsx @@ -48,6 +48,7 @@ interface LocationConflictModalProps { onCloseClick: () => void cutoutId: CutoutId deckDef: DeckDefinition + robotName: string missingLabwareDisplayName?: string | null requiredFixtureId?: CutoutFixtureId requiredModule?: ModuleModel @@ -60,6 +61,7 @@ export const LocationConflictModal = ( const { onCloseClick, cutoutId, + robotName, missingLabwareDisplayName, requiredFixtureId, requiredModule, @@ -153,7 +155,11 @@ export const LocationConflictModal = ( protocolSpecifiesDisplayName = getModuleDisplayName(requiredModule) } - if (showModuleSelect && requiredModule) { + const displaySlotName = isThermocycler + ? 'A1 + B1' + : getCutoutDisplayName(cutoutId) + + if (showModuleSelect && requiredModule != null) { return createPortal( , getTopPortalEl() ) } + return createPortal( isOnDevice ? ( - {t('slot_location', { - slotName: isThermocycler - ? 'A1 + B1' - : getCutoutDisplayName(cutoutId), - })} + {t('slot_location', { slotName: displaySlotName })} - {t('slot_location', { - slotName: isThermocycler - ? 'A1 + B1' - : getCutoutDisplayName(cutoutId), - })} + {t('slot_location', { slotName: displaySlotName })} - {t('add_fixture_to_deck')} + {t('add_this_deck_hardware')} { - const { deckConfigCompatibility } = props + const { deckConfigCompatibility, robotName } = props const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) return ( <> @@ -53,6 +55,7 @@ export const SetupFixtureList = (props: SetupFixtureListProps): JSX.Element => { ) @@ -63,6 +66,7 @@ export const SetupFixtureList = (props: SetupFixtureListProps): JSX.Element => { interface FixtureListItemProps extends CutoutConfigAndCompatibility { deckDef: DeckDefinition + robotName: string } export function FixtureListItem({ @@ -71,6 +75,7 @@ export function FixtureListItem({ compatibleCutoutFixtureIds, missingLabwareDisplayName, deckDef, + robotName, }: FixtureListItemProps): JSX.Element { const { t } = useTranslation('protocol_setup') @@ -135,6 +140,7 @@ export function FixtureListItem({ deckDef={deckDef} missingLabwareDisplayName={missingLabwareDisplayName} requiredFixtureId={compatibleCutoutFixtureIds[0]} + robotName={robotName} /> ) : null} {showSetupInstructionsModal ? ( diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesList.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesList.tsx index cf258c2bc00..4024fcac296 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesList.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesList.tsx @@ -127,6 +127,7 @@ export const SetupModulesList = (props: SetupModulesListProps): JSX.Element => { calibrationStatus={calibrationStatus} conflictedFixture={conflictedFixture} deckDef={deckDef} + robotName={robotName} /> ) } @@ -145,6 +146,7 @@ interface ModulesListItemProps { calibrationStatus: ProtocolCalibrationStatus deckDef: DeckDefinition conflictedFixture: CutoutConfig | null + robotName: string } export function ModulesListItem({ @@ -157,6 +159,7 @@ export function ModulesListItem({ calibrationStatus, conflictedFixture, deckDef, + robotName, }: ModulesListItemProps): JSX.Element { const { t } = useTranslation(['protocol_setup', 'module_wizard_flows']) const moduleConnectionStatus = @@ -286,6 +289,7 @@ export function ModulesListItem({ cutoutId={cutoutIdForSlotName} requiredModule={moduleModel} deckDef={deckDef} + robotName={robotName} /> ) : null} {showModuleWizard && attachedModuleMatch != null ? ( diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx index a5c6ab1ecbd..76aea98a8cc 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx @@ -19,8 +19,10 @@ import { ModuleInfo } from '../../ModuleInfo' import { useAttachedModules, useStoredProtocolAnalysis } from '../../hooks' import { getProtocolModulesInfo } from '../utils/getProtocolModulesInfo' import { getStandardDeckViewLayerBlockList } from '../utils/getStandardDeckViewLayerBlockList' +import { useDeckConfigurationQuery } from '@opentrons/react-api-client' const ATTACHED_MODULE_POLL_MS = 5000 +const DECK_CONFIG_POLL_MS = 5000 interface SetupModulesMapProps { runId: string @@ -33,7 +35,9 @@ export const SetupModulesMap = ({ const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) const protocolAnalysis = robotProtocolAnalysis ?? storedProtocolAnalysis - + const { data: actualDeckConfig = [] } = useDeckConfigurationQuery({ + refetchInterval: DECK_CONFIG_POLL_MS, + }) const attachedModules = useAttachedModules({ refetchInterval: ATTACHED_MODULE_POLL_MS, @@ -44,11 +48,13 @@ export const SetupModulesMap = ({ const robotType = protocolAnalysis.robotType ?? FLEX_ROBOT_TYPE const deckDef = getDeckDefFromRobotType(robotType) + const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef) const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( attachedModules, - protocolModulesInfo + protocolModulesInfo, + actualDeckConfig ) const modulesOnDeck = attachedProtocolModuleMatches.map(module => ({ @@ -64,7 +70,9 @@ export const SetupModulesMap = ({ ), })) - const deckConfig = getSimplestDeckConfigForProtocol(protocolAnalysis) + const simplestProtocolDeckConfig = getSimplestDeckConfigForProtocol( + protocolAnalysis + ) return ( ({ - cutoutId, - cutoutFixtureId, - }))} + deckConfig={simplestProtocolDeckConfig.map( + ({ cutoutId, cutoutFixtureId }) => ({ + cutoutId, + cutoutFixtureId, + }) + )} deckLayerBlocklist={getStandardDeckViewLayerBlockList(robotType)} robotType={robotType} labwareOnDeck={[]} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/LocationConflictModal.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/LocationConflictModal.test.tsx index 2557f0ba001..d72a00a9f5f 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/LocationConflictModal.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/LocationConflictModal.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import { UseQueryResult } from 'react-query' +import { MemoryRouter } from 'react-router-dom' import { screen, fireEvent } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' @@ -16,12 +17,14 @@ import { useUpdateDeckConfigurationMutation, } from '@opentrons/react-api-client' import { i18n } from '../../../../../i18n' +import { mockHeaterShaker } from '../../../../../redux/modules/__fixtures__' +import { useCloseCurrentRun } from '../../../../ProtocolUpload/hooks' import { LocationConflictModal } from '../LocationConflictModal' import type { DeckConfiguration } from '@opentrons/shared-data' -import { mockHeaterShaker } from '../../../../../redux/modules/__fixtures__' vi.mock('@opentrons/react-api-client') +vi.mock('../../../../ProtocolUpload/hooks') const mockFixture = { cutoutId: 'cutoutB3', @@ -29,9 +32,14 @@ const mockFixture = { } const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + )[0] } describe('LocationConflictModal', () => { @@ -43,7 +51,11 @@ describe('LocationConflictModal', () => { cutoutId: 'cutoutB3', requiredFixtureId: TRASH_BIN_ADAPTER_FIXTURE, deckDef: ot3StandardDeckV5 as any, + robotName: 'otie', } + vi.mocked(useCloseCurrentRun).mockReturnValue({ + closeCurrentRun: vi.fn(), + } as any) vi.mocked(useModulesQuery).mockReturnValue({ data: { data: [] } } as any) vi.mocked(useDeckConfigurationQuery).mockReturnValue({ data: [mockFixture], @@ -77,6 +89,7 @@ describe('LocationConflictModal', () => { cutoutId: 'cutoutB3', requiredModule: 'heaterShakerModuleV1', deckDef: ot3StandardDeckV5 as any, + robotName: 'otie', } render(props) screen.getByText('Protocol specifies') @@ -103,6 +116,7 @@ describe('LocationConflictModal', () => { requiredFixtureId: SINGLE_RIGHT_SLOT_FIXTURE, missingLabwareDisplayName: 'a tiprack', deckDef: ot3StandardDeckV5 as any, + robotName: 'otie', } render(props) screen.getByText('Deck location conflict') diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx index f2adbfe736d..b124a000f53 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx @@ -41,7 +41,7 @@ describe('NotConfiguredModal', () => { const { getByText, getByRole } = render(props) getByText('Add Trash bin to deck configuration') getByText( - 'Add this fixture to your deck configuration. It will be referenced during protocol analysis.' + 'Add this deck hardware to your deck configuration. It will be referenced during protocol analysis.' ) getByText('Trash bin') fireEvent.click(getByRole('button', { name: 'Add' })) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx index 2aba1928899..3571eef7b31 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx @@ -69,6 +69,7 @@ describe('SetupFixtureList', () => { beforeEach(() => { props = { deckConfigCompatibility: mockDeckConfigCompatibility, + robotName: 'otie', } vi.mocked(LocationConflictModal).mockReturnValue(
mock location conflict modal
@@ -100,6 +101,7 @@ describe('SetupFixtureList', () => { it('should render the headers and a fixture with conflicted status', () => { props = { deckConfigCompatibility: mockConflictDeckConfigCompatibility, + robotName: 'otie', } render(props) screen.getByText('Location conflict') @@ -110,6 +112,7 @@ describe('SetupFixtureList', () => { it('should render the headers and a fixture with not configured status and button', () => { props = { deckConfigCompatibility: mockNotConfiguredDeckConfigCompatibility, + robotName: 'otie', } render(props) screen.getByText('Not configured') diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx index f1e06c2471a..0de1a163356 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx @@ -121,6 +121,7 @@ export const SetupModuleAndDeck = ({ {requiredDeckConfigCompatibility.length > 0 ? ( ) : null}
diff --git a/app/src/organisms/LabwarePositionCheck/AttachProbe.tsx b/app/src/organisms/LabwarePositionCheck/AttachProbe.tsx index 5f66b98a8cf..9cf7f86f375 100644 --- a/app/src/organisms/LabwarePositionCheck/AttachProbe.tsx +++ b/app/src/organisms/LabwarePositionCheck/AttachProbe.tsx @@ -96,7 +96,11 @@ export const AttachProbe = (props: AttachProbeProps): JSX.Element | null => { const verifyCommands: CreateCommand[] = [ { commandType: 'verifyTipPresence', - params: { pipetteId: pipetteId, expectedState: 'present' }, + params: { + pipetteId: pipetteId, + expectedState: 'present', + followSingularSensor: 'primary', + }, }, ] const homeCommands: CreateCommand[] = [ diff --git a/app/src/organisms/ModuleWizardFlows/SelectLocation.tsx b/app/src/organisms/ModuleWizardFlows/SelectLocation.tsx index af0301549d0..2c78ecfb26b 100644 --- a/app/src/organisms/ModuleWizardFlows/SelectLocation.tsx +++ b/app/src/organisms/ModuleWizardFlows/SelectLocation.tsx @@ -174,6 +174,13 @@ export const SelectLocation = ( handleClickAdd={handleAddFixture} handleClickRemove={handleRemoveFixture} editableCutoutIds={editableCutoutIds} + selectedCutoutId={ + deckConfig.find( + ({ cutoutId, opentronsModuleSerialNumber }) => + Object.keys(configuredFixtureIdByCutoutId).includes(cutoutId) && + attachedModule.serialNumber === opentronsModuleSerialNumber + )?.cutoutId + } height="250px" /> } diff --git a/app/src/organisms/PipetteWizardFlows/AttachProbe.tsx b/app/src/organisms/PipetteWizardFlows/AttachProbe.tsx index 74e910758f7..a53d25a6d82 100644 --- a/app/src/organisms/PipetteWizardFlows/AttachProbe.tsx +++ b/app/src/organisms/PipetteWizardFlows/AttachProbe.tsx @@ -79,7 +79,11 @@ export const AttachProbe = (props: AttachProbeProps): JSX.Element | null => { const verifyCommands: CreateCommand[] = [ { commandType: 'verifyTipPresence', - params: { pipetteId: pipetteId, expectedState: 'present' }, + params: { + pipetteId: pipetteId, + expectedState: 'present', + followSingularSensor: 'primary', + }, }, ] const homeCommands: CreateCommand[] = [ diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/AttachProbe.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/AttachProbe.test.tsx index 3043558a5da..75af3b08f8d 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/AttachProbe.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/AttachProbe.test.tsx @@ -71,7 +71,11 @@ describe('AttachProbe', () => { [ { commandType: 'verifyTipPresence', - params: { pipetteId: 'abc', expectedState: 'present' }, + params: { + pipetteId: 'abc', + expectedState: 'present', + followSingularSensor: 'primary', + }, }, ], false @@ -205,7 +209,11 @@ describe('AttachProbe', () => { [ { commandType: 'verifyTipPresence', - params: { pipetteId: 'abc', expectedState: 'present' }, + params: { + pipetteId: 'abc', + expectedState: 'present', + followSingularSensor: 'primary', + }, }, ], false diff --git a/app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx b/app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx index b44a314983a..18f02ec5e5c 100644 --- a/app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx +++ b/app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx @@ -7,8 +7,12 @@ import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' import { useCreateLiveCommandMutation, useModulesQuery, + useDeckConfigurationQuery, } from '@opentrons/react-api-client' -import { ot3StandardDeckV5 as ot3StandardDeckDef } from '@opentrons/shared-data' +import { + HEATERSHAKER_MODULE_V1_FIXTURE, + ot3StandardDeckV5 as ot3StandardDeckDef, +} from '@opentrons/shared-data' import { renderWithProviders } from '../../../__testing-utils__' import { i18n } from '../../../i18n' @@ -33,6 +37,7 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { ...actual, useCreateLiveCommandMutation: vi.fn(), useModulesQuery: vi.fn(), + useDeckConfigurationQuery: vi.fn(), } }) @@ -76,6 +81,16 @@ describe('ProtocolSetupLabware', () => { vi.mocked(useCreateLiveCommandMutation).mockReturnValue({ createLiveCommand: mockCreateLiveCommand, } as any) + vi.mocked(useDeckConfigurationQuery).mockReturnValue({ + data: [ + { + cutoutId: 'cutoutB1', + cutoutFixtureId: HEATERSHAKER_MODULE_V1_FIXTURE, + opentronsModuleSerialNumber: + mockUseModulesQueryClosed.data.data[0].serialNumber, + }, + ], + } as any) }) afterEach(() => { vi.clearAllMocks() diff --git a/app/src/organisms/ProtocolSetupLabware/index.tsx b/app/src/organisms/ProtocolSetupLabware/index.tsx index 3bc1ad62c56..33e44ab7534 100644 --- a/app/src/organisms/ProtocolSetupLabware/index.tsx +++ b/app/src/organisms/ProtocolSetupLabware/index.tsx @@ -34,6 +34,7 @@ import { import { parseInitialLoadedLabwareByAdapter } from '@opentrons/api-client' import { useCreateLiveCommandMutation, + useDeckConfigurationQuery, useModulesQuery, } from '@opentrons/react-api-client' @@ -64,7 +65,8 @@ import type { SetupScreens } from '../../pages/ProtocolSetup' import type { AttachedProtocolModuleMatch } from '../ProtocolSetupModulesAndDeck/utils' import { LabwareMapViewModal } from './LabwareMapViewModal' -const MODULE_REFETCH_INTERVAL = 5000 +const MODULE_REFETCH_INTERVAL_MS = 5000 +const DECK_CONFIG_POLL_MS = 5000 const LabwareThumbnail = styled.svg` transform: scale(1, -1); @@ -97,11 +99,14 @@ export function ProtocolSetupLabware({ const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) + const { data: deckConfig = [] } = useDeckConfigurationQuery({ + refetchInterval: DECK_CONFIG_POLL_MS, + }) const { offDeckItems, onDeckItems } = getLabwareSetupItemGroups( mostRecentAnalysis?.commands ?? [] ) const moduleQuery = useModulesQuery({ - refetchInterval: MODULE_REFETCH_INTERVAL, + refetchInterval: MODULE_REFETCH_INTERVAL_MS, }) const attachedModules = moduleQuery?.data?.data ?? [] const protocolModulesInfo = @@ -111,7 +116,8 @@ export function ProtocolSetupLabware({ const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( attachedModules, - protocolModulesInfo + protocolModulesInfo, + deckConfig ) const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter( mostRecentAnalysis?.commands ?? [] diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx index 23d490af287..82c9d9670f3 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx @@ -5,7 +5,6 @@ import { BORDERS, COLORS, Chip, - DIRECTION_COLUMN, DIRECTION_ROW, Flex, JUSTIFY_SPACE_BETWEEN, @@ -15,6 +14,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { + FLEX_MODULE_ADDRESSABLE_AREAS, getCutoutDisplayName, getDeckDefFromRobotType, getFixtureDisplayName, @@ -36,6 +36,8 @@ import type { } from '@opentrons/shared-data' import type { SetupScreens } from '../../pages/ProtocolSetup' import type { CutoutConfigAndCompatibility } from '../../resources/deck_configuration/hooks' +import { useSelector } from 'react-redux' +import { getLocalRobot } from '../../redux/discovery' interface FixtureTableProps { robotType: RobotType @@ -45,6 +47,11 @@ interface FixtureTableProps { setProvidedFixtureOptions: (providedFixtureOptions: CutoutFixtureId[]) => void } +/** + * Table of all "non-module" fixtures e.g. staging slot, waste chute, trash bin... + * @param props + * @returns JSX.Element + */ export function FixtureTable({ robotType, mostRecentAnalysis, @@ -52,8 +59,6 @@ export function FixtureTable({ setCutoutId, setProvidedFixtureOptions, }: FixtureTableProps): JSX.Element | null { - const { t } = useTranslation('protocol_setup') - const requiredFixtureDetails = getSimplestDeckConfigForProtocol( mostRecentAnalysis ) @@ -62,6 +67,8 @@ export function FixtureTable({ mostRecentAnalysis ) const deckDef = getDeckDefFromRobotType(robotType) + const localRobot = useSelector(getLocalRobot) + const robotName = localRobot?.name != null ? localRobot.name : '' const requiredDeckConfigCompatibility = getRequiredDeckConfig( deckConfigCompatibility @@ -77,21 +84,11 @@ export function FixtureTable({ ) return sortedDeckConfigCompatibility.length > 0 ? ( - - - {t('fixture')} - {t('location')} - {t('status')} - + <> {sortedDeckConfigCompatibility.map((fixtureCompatibility, index) => { - return ( + return fixtureCompatibility.requiredAddressableAreas.some(raa => + FLEX_MODULE_ADDRESSABLE_AREAS.includes(raa) + ) ? null : ( ) })} - + ) : null } @@ -113,6 +111,7 @@ interface FixtureTableItemProps extends CutoutConfigAndCompatibility { setCutoutId: (cutoutId: CutoutId) => void setProvidedFixtureOptions: (providedFixtureOptions: CutoutFixtureId[]) => void deckDef: DeckDefinition + robotName: string } function FixtureTableItem({ @@ -125,6 +124,7 @@ function FixtureTableItem({ setCutoutId, setProvidedFixtureOptions, deckDef, + robotName, }: FixtureTableItemProps): JSX.Element { const { t, i18n } = useTranslation('protocol_setup') @@ -190,6 +190,7 @@ function FixtureTableItem({ isOnDevice={true} missingLabwareDisplayName={missingLabwareDisplayName} deckDef={deckDef} + robotName={robotName} /> ) : null} - - {t('module')} - {t('location')} - {t('status')} - + <> {attachedProtocolModuleMatches.map(module => { - const cutoutIdForSlotName = getCutoutIdForSlotName( + const moduleFixtures = getCutoutFixturesForModuleModel( + module.moduleDef.model, + deckDef + ) + const moduleCutoutIds = getCutoutIdsFromModuleSlotName( module.slotName, + moduleFixtures, deckDef ) - - const isMagneticBlockModule = - module.moduleDef.moduleType === MAGNETIC_BLOCK_TYPE - - const isThermocycler = - module.moduleDef.moduleType === THERMOCYCLER_MODULE_TYPE - const conflictedFixture = deckConfig?.find( - fixture => - (fixture.cutoutId === cutoutIdForSlotName || - // special-case A1 for the thermocycler to require a single slot fixture - (fixture.cutoutId === 'cutoutA1' && isThermocycler)) && - fixture.cutoutFixtureId != null && - // do not generate a conflict for single slot fixtures, because modules are not yet fixtures - !SINGLE_SLOT_FIXTURES.includes(fixture.cutoutFixtureId) && - // special case the magnetic module because unlike other modules it sits in a slot that can also be provided by a staging area fixture - (!isMagneticBlockModule || - fixture.cutoutFixtureId !== STAGING_AREA_RIGHT_SLOT_FIXTURE) + ({ cutoutId, cutoutFixtureId }) => + moduleCutoutIds.includes(cutoutId) && + !moduleFixtures.some(({ id }) => cutoutFixtureId === id) && + module.attachedModuleMatch == null ) ?? null - return ( ) })} - + ) } @@ -140,6 +115,7 @@ interface ModuleTableItemProps { prepCommandErrorMessage: string setPrepCommandErrorMessage: React.Dispatch> deckDef: DeckDefinition + robotName: string } function ModuleTableItem({ @@ -151,6 +127,7 @@ function ModuleTableItem({ setPrepCommandErrorMessage, conflictedFixture, deckDef, + robotName, }: ModuleTableItemProps): JSX.Element { const { i18n, t } = useTranslation(['protocol_setup', 'module_wizard_flows']) @@ -276,6 +253,7 @@ function ModuleTableItem({ requiredModule={module.moduleDef.model} deckDef={deckDef} isOnDevice={true} + robotName={robotName} /> ) : null} { setCutoutId: mockSetCutoutId, setProvidedFixtureOptions: mockSetProvidedFixtureOptions, } + vi.mocked(getLocalRobot).mockReturnValue(mockConnectedRobot) vi.mocked(LocationConflictModal).mockReturnValue(
mock location conflict modal
) @@ -60,13 +64,6 @@ describe('FixtureTable', () => { vi.clearAllMocks() }) - it('should render table header and contents', () => { - render(props) - screen.getByText('Fixture') - screen.getByText('Location') - screen.getByText('Status') - }) - it('should render the current status - configured', () => { render(props) screen.getByText('Configured') diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx index ead32d65d38..bf2dfbe5dc4 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx @@ -107,7 +107,7 @@ describe('ProtocolSetupModulesAndDeck', () => { .calledWith(mockRobotSideAnalysis, flexDeckDef) .thenReturn([]) when(vi.mocked(getAttachedProtocolModuleMatches)) - .calledWith([], []) + .calledWith([], [], []) .thenReturn([]) when(vi.mocked(getUnmatchedModulesForProtocol)) .calledWith([], []) @@ -148,7 +148,7 @@ describe('ProtocolSetupModulesAndDeck', () => { }, ]) render() - screen.getByText('Module') + screen.getByText('Deck hardware') screen.getByText('Location') screen.getByText('Status') screen.getByText('Setup Instructions') @@ -313,7 +313,7 @@ describe('ProtocolSetupModulesAndDeck', () => { vi.mocked(getAttachedProtocolModuleMatches).mockReturnValue([ { ...mockProtocolModuleInfo[0], - attachedModuleMatch: calibratedMockApiHeaterShaker, + attachedModuleMatch: undefined, slotName: 'D3', }, ]) diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx index 97c76148799..b96d972ca36 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx @@ -1,7 +1,10 @@ import { describe, it, expect } from 'vitest' -import { getModuleDef2 } from '@opentrons/shared-data' +import { + TEMPERATURE_MODULE_V2_FIXTURE, + getModuleDef2, +} from '@opentrons/shared-data' -import { mockTemperatureModule } from '../../../redux/modules/__fixtures__' +import { mockTemperatureModuleGen2 } from '../../../redux/modules/__fixtures__' import { getAttachedProtocolModuleMatches, getUnmatchedModulesForProtocol, @@ -12,7 +15,7 @@ const temperatureProtocolModule = { x: 0, y: 0, z: 0, - moduleDef: getModuleDef2('temperatureModuleV1'), + moduleDef: getModuleDef2('temperatureModuleV2'), nestedLabwareDef: null, nestedLabwareId: null, nestedLabwareDisplayName: null, @@ -37,7 +40,8 @@ describe('getAttachedProtocolModuleMatches', () => { it('returns no module matches when no modules attached', () => { const result = getAttachedProtocolModuleMatches( [], - [temperatureProtocolModule, magneticProtocolModule] + [temperatureProtocolModule, magneticProtocolModule], + [] ) expect(result).toEqual([ { ...temperatureProtocolModule, attachedModuleMatch: null }, @@ -47,8 +51,15 @@ describe('getAttachedProtocolModuleMatches', () => { it('returns no module matches when no modules match', () => { const result = getAttachedProtocolModuleMatches( - [mockTemperatureModule], - [magneticProtocolModule] + [mockTemperatureModuleGen2], + [magneticProtocolModule], + [ + { + cutoutId: 'cutoutD1', + cutoutFixtureId: TEMPERATURE_MODULE_V2_FIXTURE, + opentronsModuleSerialNumber: mockTemperatureModuleGen2.serialNumber, + }, + ] ) expect(result).toEqual([ { ...magneticProtocolModule, attachedModuleMatch: null }, @@ -57,13 +68,20 @@ describe('getAttachedProtocolModuleMatches', () => { it('returns module match when modules match', () => { const result = getAttachedProtocolModuleMatches( - [mockTemperatureModule], - [temperatureProtocolModule, magneticProtocolModule] + [mockTemperatureModuleGen2], + [temperatureProtocolModule, magneticProtocolModule], + [ + { + cutoutId: 'cutoutD1', + cutoutFixtureId: TEMPERATURE_MODULE_V2_FIXTURE, + opentronsModuleSerialNumber: mockTemperatureModuleGen2.serialNumber, + }, + ] ) expect(result).toEqual([ { ...temperatureProtocolModule, - attachedModuleMatch: mockTemperatureModule, + attachedModuleMatch: mockTemperatureModuleGen2, }, { ...magneticProtocolModule, attachedModuleMatch: null }, ]) @@ -81,7 +99,7 @@ describe('getUnmatchedModulesForProtocol', () => { it('returns no missing module ids or remaining attached modules when attached modules match', () => { const result = getUnmatchedModulesForProtocol( - [mockTemperatureModule], + [mockTemperatureModuleGen2], [temperatureProtocolModule] ) expect(result).toEqual({ @@ -103,12 +121,12 @@ describe('getUnmatchedModulesForProtocol', () => { it('returns remaining attached modules when protocol modules and attached modules do not match', () => { const result = getUnmatchedModulesForProtocol( - [mockTemperatureModule], + [mockTemperatureModuleGen2], [magneticProtocolModule] ) expect(result).toEqual({ missingModuleIds: ['mockMagneticModuleId'], - remainingAttachedModules: [mockTemperatureModule], + remainingAttachedModules: [mockTemperatureModuleGen2], }) }) }) diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx index 3a03a91c9c6..86f51d42afe 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx @@ -2,7 +2,14 @@ import * as React from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' -import { DIRECTION_COLUMN, Flex, SPACING } from '@opentrons/components' +import { + COLORS, + DIRECTION_COLUMN, + Flex, + SPACING, + StyledText, + TYPOGRAPHY, +} from '@opentrons/components' import { FLEX_ROBOT_TYPE, getDeckDefFromRobotType, @@ -26,8 +33,10 @@ import { ModulesAndDeckMapViewModal } from './ModulesAndDeckMapViewModal' import type { CutoutId, CutoutFixtureId } from '@opentrons/shared-data' import type { SetupScreens } from '../../pages/ProtocolSetup' +import { useDeckConfigurationQuery } from '@opentrons/react-api-client' const ATTACHED_MODULE_POLL_MS = 5000 +const DECK_CONFIG_POLL_MS = 5000 interface ProtocolSetupModulesAndDeckProps { runId: string @@ -59,7 +68,9 @@ export function ProtocolSetupModulesAndDeck({ const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) - + const { data: deckConfig = [] } = useDeckConfigurationQuery({ + refetchInterval: DECK_CONFIG_POLL_MS, + }) const attachedModules = useAttachedModules({ refetchInterval: ATTACHED_MODULE_POLL_MS, @@ -72,7 +83,8 @@ export function ProtocolSetupModulesAndDeck({ const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( attachedModules, - protocolModulesInfo + protocolModulesInfo, + deckConfig ) const hasModules = attachedProtocolModuleMatches.length > 0 @@ -105,7 +117,7 @@ export function ProtocolSetupModulesAndDeck({ getTopPortalEl() )} setSetupScreen('prepare to run')} buttonText={i18n.format(t('setup_instructions'), 'titleCase')} buttonType="tertiaryLowLight" @@ -116,7 +128,7 @@ export function ProtocolSetupModulesAndDeck({ {isModuleMismatch && !clearModuleMismatchBanner ? ( @@ -131,20 +143,36 @@ export function ProtocolSetupModulesAndDeck({ /> ) : null} - {hasModules ? ( - + + {i18n.format(t('deck_hardware'), 'titleCase')} + + {t('location')} + {t('status')} + + + {hasModules ? ( + + ) : null} + - ) : null} - +
setShowDeckMapModal(true)} /> diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts b/app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts index 113cba73075..cc921ef6049 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts @@ -1,6 +1,11 @@ import { + DeckConfiguration, + FLEX_ROBOT_TYPE, NON_CONNECTING_MODULE_TYPES, checkModuleCompatibility, + getCutoutFixturesForModuleModel, + getCutoutIdsFromModuleSlotName, + getDeckDefFromRobotType, getModuleType, } from '@opentrons/shared-data' @@ -11,14 +16,26 @@ export type AttachedProtocolModuleMatch = ProtocolModuleInfo & { attachedModuleMatch: AttachedModule | null } +// NOTE: this is a FLEX only function // some logic copied from useModuleRenderInfoForProtocolById export function getAttachedProtocolModuleMatches( attachedModules: AttachedModule[], - protocolModulesInfo: ProtocolModuleInfo[] + protocolModulesInfo: ProtocolModuleInfo[], + deckConfig: DeckConfiguration ): AttachedProtocolModuleMatch[] { + const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) // this is only used for Flex ODD const matchedAttachedModules: AttachedModule[] = [] const attachedProtocolModuleMatches = protocolModulesInfo.map( protocolModule => { + const moduleFixtures = getCutoutFixturesForModuleModel( + protocolModule.moduleDef.model, + deckDef + ) + const moduleCutoutIds = getCutoutIdsFromModuleSlotName( + protocolModule.slotName, + moduleFixtures, + deckDef + ) const compatibleAttachedModule = attachedModules.find( attachedModule => @@ -27,10 +44,17 @@ export function getAttachedProtocolModuleMatches( protocolModule.moduleDef.model ) && // check id instead of object reference in useModuleRenderInfoForProtocolById - matchedAttachedModules.find( + !matchedAttachedModules.some( matchedAttachedModule => - matchedAttachedModule.id === attachedModule.id - ) == null + matchedAttachedModule.serialNumber === + attachedModule.serialNumber + ) && + // check deck config has module with expected serial number in expected location + deckConfig.some( + ({ cutoutId, opentronsModuleSerialNumber }) => + attachedModule.serialNumber === opentronsModuleSerialNumber && + moduleCutoutIds.includes(cutoutId) + ) ) ?? null if (compatibleAttachedModule !== null) { matchedAttachedModules.push(compatibleAttachedModule) diff --git a/app/src/pages/ProtocolSetup/__tests__/ProtocolSetup.test.tsx b/app/src/pages/ProtocolSetup/__tests__/ProtocolSetup.test.tsx index 030e3c1a9a0..0c7497166aa 100644 --- a/app/src/pages/ProtocolSetup/__tests__/ProtocolSetup.test.tsx +++ b/app/src/pages/ProtocolSetup/__tests__/ProtocolSetup.test.tsx @@ -296,7 +296,7 @@ describe('ProtocolSetup', () => { render(`/runs/${RUN_ID}/setup/`) screen.getByText('Prepare to run') screen.getByText('Instruments') - screen.getByText('Modules & deck') + screen.getByText('Deck hardware') screen.getByText('Labware') screen.getByText('Labware Position Check') screen.getByText('Liquids') @@ -326,7 +326,7 @@ describe('ProtocolSetup', () => { .calledWith([], mockProtocolModuleInfo) .thenReturn({ missingModuleIds: [], remainingAttachedModules: [] }) render(`/runs/${RUN_ID}/setup/`) - fireEvent.click(screen.getByText('Modules & deck')) + fireEvent.click(screen.getByText('Deck hardware')) expect(vi.mocked(ProtocolSetupModulesAndDeck)).toHaveBeenCalled() }) diff --git a/app/src/pages/ProtocolSetup/index.tsx b/app/src/pages/ProtocolSetup/index.tsx index 14b871f839c..0c73eb1e50d 100644 --- a/app/src/pages/ProtocolSetup/index.tsx +++ b/app/src/pages/ProtocolSetup/index.tsx @@ -705,7 +705,7 @@ function PrepareToRun({ /> setSetupScreen('modules')} - title={t('modules_and_deck')} + title={t('deck_hardware')} detail={modulesDetail} subDetail={modulesSubDetail} status={modulesStatus} diff --git a/app/src/resources/__tests__/useNotifyService.test.ts b/app/src/resources/__tests__/useNotifyService.test.ts index fdb531ab1cd..ce513e3e572 100644 --- a/app/src/resources/__tests__/useNotifyService.test.ts +++ b/app/src/resources/__tests__/useNotifyService.test.ts @@ -14,6 +14,7 @@ import type { Mock } from 'vitest' import type { HostConfig } from '@opentrons/api-client' import type { QueryOptionsWithPolling } from '../useNotifyService' +vi.unmock('../useNotifyService') vi.mock('react-redux') vi.mock('@opentrons/react-api-client') vi.mock('../../redux/analytics') diff --git a/components/src/hardware-sim/DeckConfigurator/HeaterShakerFixture.tsx b/components/src/hardware-sim/DeckConfigurator/HeaterShakerFixture.tsx index 129fd46993a..a28448d0c51 100644 --- a/components/src/hardware-sim/DeckConfigurator/HeaterShakerFixture.tsx +++ b/components/src/hardware-sim/DeckConfigurator/HeaterShakerFixture.tsx @@ -12,6 +12,7 @@ import { FIXTURE_HEIGHT, COLUMN_3_SINGLE_SLOT_FIXTURE_WIDTH, Y_ADJUSTMENT, + CONFIG_STYLE_SELECTED, } from './constants' import type { @@ -30,6 +31,7 @@ interface HeaterShakerFixtureProps { fixtureLocation: CutoutId, cutoutFixtureId: CutoutFixtureId ) => void + selected?: boolean } const HEATER_SHAKER_MODULE_FIXTURE_DISPLAY_NAME = 'Heater-Shaker' @@ -42,6 +44,7 @@ export function HeaterShakerFixture( handleClickRemove, fixtureLocation, cutoutFixtureId, + selected = false, } = props const cutoutDef = deckDefinition.locations.cutouts.find( @@ -67,6 +70,7 @@ export function HeaterShakerFixture( const y = ySlotPosition + Y_ADJUSTMENT + const editableStyle = selected ? CONFIG_STYLE_SELECTED : CONFIG_STYLE_EDITABLE return ( void hasStagingArea?: boolean + selected?: boolean } const MAGNETIC_BLOCK_FIXTURE_DISPLAY_NAME = 'Mag Block' @@ -48,6 +50,7 @@ export function MagneticBlockFixture( handleClickRemove, cutoutFixtureId, hasStagingArea, + selected = false, } = props const standardSlotCutout = deckDefinition.locations.cutouts.find( @@ -98,6 +101,7 @@ export function MagneticBlockFixture( const y = ySlotPosition + Y_ADJUSTMENT + const editableStyle = selected ? CONFIG_STYLE_SELECTED : CONFIG_STYLE_EDITABLE return ( void + selected?: boolean } export function StagingAreaConfigFixture( @@ -39,6 +41,7 @@ export function StagingAreaConfigFixture( handleClickRemove, fixtureLocation, cutoutFixtureId, + selected = false, } = props const stagingAreaCutout = deckDefinition.locations.cutouts.find( @@ -55,6 +58,7 @@ export function StagingAreaConfigFixture( const x = xSlotPosition + COLUMN_3_X_ADJUSTMENT const y = ySlotPosition + Y_ADJUSTMENT + const editableStyle = selected ? CONFIG_STYLE_SELECTED : CONFIG_STYLE_EDITABLE return ( void + selected?: boolean } export function TemperatureModuleFixture( @@ -42,6 +44,7 @@ export function TemperatureModuleFixture( handleClickRemove, fixtureLocation, cutoutFixtureId, + selected = false, } = props const cutoutDef = deckDefinition.locations.cutouts.find( @@ -67,6 +70,8 @@ export function TemperatureModuleFixture( const y = ySlotPosition + Y_ADJUSTMENT + const editableStyle = selected ? CONFIG_STYLE_SELECTED : CONFIG_STYLE_EDITABLE + return ( void + selected?: boolean } const THERMOCYCLER_FIXTURE_DISPLAY_NAME = 'Thermocycler' @@ -39,6 +41,7 @@ export function ThermocyclerFixture( handleClickRemove, fixtureLocation, cutoutFixtureId, + selected = false, } = props const cutoutDef = deckDefinition.locations.cutouts.find( @@ -54,6 +57,7 @@ export function ThermocyclerFixture( const x = xSlotPosition + COLUMN_1_X_ADJUSTMENT const y = ySlotPosition + Y_ADJUSTMENT + const editableStyle = selected ? CONFIG_STYLE_SELECTED : CONFIG_STYLE_EDITABLE return ( void + selected?: boolean } export function TrashBinConfigFixture( @@ -40,6 +42,7 @@ export function TrashBinConfigFixture( handleClickRemove, fixtureLocation, cutoutFixtureId, + selected = false, } = props const trashBinCutout = deckDefinition.locations.cutouts.find( @@ -65,6 +68,7 @@ export function TrashBinConfigFixture( const y = ySlotPosition + Y_ADJUSTMENT + const editableStyle = selected ? CONFIG_STYLE_SELECTED : CONFIG_STYLE_EDITABLE return ( void hasStagingAreas?: boolean + selected?: boolean } export function WasteChuteConfigFixture( @@ -42,6 +44,7 @@ export function WasteChuteConfigFixture( fixtureLocation, cutoutFixtureId, hasStagingAreas = false, + selected = false, } = props const wasteChuteCutout = deckDefinition.locations.cutouts.find( @@ -58,6 +61,7 @@ export function WasteChuteConfigFixture( const x = xSlotPosition + COLUMN_3_X_ADJUSTMENT const y = ySlotPosition + Y_ADJUSTMENT + const editableStyle = selected ? CONFIG_STYLE_SELECTED : CONFIG_STYLE_EDITABLE return ( height?: string + selectedCutoutId?: CutoutId } export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { @@ -58,6 +59,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { handleClickRemove, additionalStaticFixtures, children, + selectedCutoutId, lightFill = COLORS.grey35, darkFill = COLORS.black90, editableCutoutIds = deckConfig.map(({ cutoutId }) => cutoutId), @@ -107,7 +109,6 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { return ( {deckDef.locations.cutouts.map(cutout => ( @@ -131,6 +132,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { } fixtureLocation={cutoutId} cutoutFixtureId={cutoutFixtureId} + selected={cutoutId === selectedCutoutId} /> ))} {emptyCutouts.map(({ cutoutId }) => ( @@ -150,6 +152,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { } fixtureLocation={cutoutId} cutoutFixtureId={cutoutFixtureId} + selected={cutoutId === selectedCutoutId} /> ))} {wasteChuteStagingAreaFixtures.map(({ cutoutId, cutoutFixtureId }) => ( @@ -161,6 +164,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { } fixtureLocation={cutoutId} cutoutFixtureId={cutoutFixtureId} + selected={cutoutId === selectedCutoutId} hasStagingAreas /> ))} @@ -173,6 +177,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { } fixtureLocation={cutoutId} cutoutFixtureId={cutoutFixtureId} + selected={cutoutId === selectedCutoutId} /> ))} {temperatureModuleFixtures.map(({ cutoutId, cutoutFixtureId }) => ( @@ -184,6 +189,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { } fixtureLocation={cutoutId} cutoutFixtureId={cutoutFixtureId} + selected={cutoutId === selectedCutoutId} /> ))} {heaterShakerFixtures.map(({ cutoutId, cutoutFixtureId }) => ( @@ -195,6 +201,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { } fixtureLocation={cutoutId} cutoutFixtureId={cutoutFixtureId} + selected={cutoutId === selectedCutoutId} /> ))} {magneticBlockFixtures.map(({ cutoutId, cutoutFixtureId }) => ( @@ -206,6 +213,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { } fixtureLocation={cutoutId} cutoutFixtureId={cutoutFixtureId} + selected={cutoutId === selectedCutoutId} hasStagingArea={ cutoutFixtureId === STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE } @@ -220,6 +228,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { } fixtureLocation={cutoutId} cutoutFixtureId={cutoutFixtureId} + selected={cutoutId === selectedCutoutId} /> ))} {additionalStaticFixtures?.map(staticFixture => ( diff --git a/setup-vitest.ts b/setup-vitest.ts index bf9d07a6ba7..eb30f021428 100644 --- a/setup-vitest.ts +++ b/setup-vitest.ts @@ -7,6 +7,7 @@ vi.mock('electron-store') vi.mock('electron-updater') vi.mock('electron') vi.mock('./app/src/redux/shell/remote') +vi.mock('./app/src/resources/useNotifyService') process.env.OT_PD_VERSION = 'fake_PD_version' global._PKG_VERSION_ = 'test environment' diff --git a/shared-data/command/schemas/8.json b/shared-data/command/schemas/8.json index f3c5bb38b27..97b60561fa2 100644 --- a/shared-data/command/schemas/8.json +++ b/shared-data/command/schemas/8.json @@ -2582,6 +2582,12 @@ "enum": ["present", "absent", "unknown"], "type": "string" }, + "InstrumentSensorId": { + "title": "InstrumentSensorId", + "description": "Primary and secondary sensor ids.", + "enum": ["primary", "secondary", "both"], + "type": "string" + }, "VerifyTipPresenceParams": { "title": "VerifyTipPresenceParams", "description": "Payload required for a VerifyTipPresence command.", @@ -2599,6 +2605,14 @@ "$ref": "#/definitions/TipPresenceStatus" } ] + }, + "followSingularSensor": { + "description": "The sensor id to follow if the other can be ignored.", + "allOf": [ + { + "$ref": "#/definitions/InstrumentSensorId" + } + ] } }, "required": ["pipetteId", "expectedState"] diff --git a/shared-data/command/types/pipetting.ts b/shared-data/command/types/pipetting.ts index a7364add50b..57a11a0621e 100644 --- a/shared-data/command/types/pipetting.ts +++ b/shared-data/command/types/pipetting.ts @@ -282,6 +282,7 @@ interface WellLocationParam { interface VerifyTipPresenceParams extends PipetteIdentityParams { expectedState?: 'present' | 'absent' + followSingularSensor?: 'primary' | 'secondary' } interface BasicLiquidHandlingResult { diff --git a/shared-data/python/opentrons_shared_data/performance/__init__.py b/shared-data/python/opentrons_shared_data/performance/__init__.py new file mode 100644 index 00000000000..8cdbffce690 --- /dev/null +++ b/shared-data/python/opentrons_shared_data/performance/__init__.py @@ -0,0 +1 @@ +"""Performance metrics.""" diff --git a/shared-data/python/tests/performance/__init__.py b/shared-data/python/tests/performance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/shared-data/python/tests/performance/test_module_builds.py b/shared-data/python/tests/performance/test_module_builds.py new file mode 100644 index 00000000000..d52b5a59779 --- /dev/null +++ b/shared-data/python/tests/performance/test_module_builds.py @@ -0,0 +1,6 @@ +from pathlib import Path +from opentrons_shared_data.performance.dev_types import RobotContextState + + +def test_metrics_metadata(tmp_path: Path) -> None: + RobotContextState.ANALYZING_PROTOCOL