diff --git a/opentrons-ai-server/Pipfile b/opentrons-ai-server/Pipfile index 947046431b3..511aaae2b61 100644 --- a/opentrons-ai-server/Pipfile +++ b/opentrons-ai-server/Pipfile @@ -14,6 +14,8 @@ ddtrace = "==2.9.2" pydantic-settings = "==2.3.4" pyjwt = {extras = ["crypto"], version = "*"} python-json-logger = "==2.0.7" +beautifulsoup4 = "==4.12.3" +markdownify = "==0.13.1" [dev-packages] docker = "==7.1.0" @@ -27,6 +29,7 @@ boto3-stubs = "==1.34.114" rich = "==13.7.1" cryptography = "==42.0.7" types-docker = "==7.0.0.20240528" +types-beautifulsoup4 = "*" [requires] python_version = "3.12" diff --git a/opentrons-ai-server/Pipfile.lock b/opentrons-ai-server/Pipfile.lock index 3ca2c236564..5b55e8c1ea6 100644 --- a/opentrons-ai-server/Pipfile.lock +++ b/opentrons-ai-server/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "cb12ec3ffaa00df575c6e86536481c155a47af29a9a638cba8999b39f7bc28a6" + "sha256": "e2bc86bbccb5f6ac73c2e1e72eb1c92164fb6712e5672b31b60c97ea53cf94fd" }, "pipfile-spec": 6, "requires": { @@ -136,6 +136,7 @@ "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed" ], + "index": "pypi", "markers": "python_full_version >= '3.6.0'", "version": "==4.12.3" }, @@ -157,11 +158,11 @@ }, "certifi": { "hashes": [ - "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", - "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" + "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", + "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" ], "markers": "python_version >= '3.6'", - "version": "==2024.6.2" + "version": "==2024.7.4" }, "cffi": { "hashes": [ @@ -327,40 +328,35 @@ }, "cryptography": { "hashes": [ - "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad", - "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", - "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", - "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", - "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", - "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648", - "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", - "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", - "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c", - "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", - "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", - "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", - "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", - "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", - "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", - "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", - "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", - "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", - "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", - "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", - "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", - "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", - "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", - "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", - "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", - "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", - "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", - "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842", - "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", - "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", - "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", - "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e" - ], - "version": "==42.0.8" + "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709", + "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069", + "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2", + "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b", + "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e", + "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70", + "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778", + "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22", + "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895", + "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf", + "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431", + "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f", + "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947", + "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74", + "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc", + "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66", + "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66", + "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf", + "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f", + "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5", + "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e", + "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f", + "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55", + "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1", + "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47", + "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5", + "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0" + ], + "version": "==43.0.0" }, "dataclasses-json": { "hashes": [ @@ -486,11 +482,11 @@ }, "envier": { "hashes": [ - "sha256:b45ef6051fea33d0c32a64e186bff2cfb446e2242d6781216c9bc9ce708c5909", - "sha256:bd5ccf707447973ea0f4125b7df202ba415ad888bcdcb8df80e0b002ee11ffdb" + "sha256:4e7e398cb09a8dd360508ef7e12511a152355426d2544b8487a34dad27cc20ad", + "sha256:65099cf3aa9b3b3b4b92db2f7d29e2910672e085b76f7e587d2167561a834add" ], "markers": "python_version >= '3.7'", - "version": "==0.5.1" + "version": "==0.5.2" }, "fastapi": { "hashes": [ @@ -781,11 +777,11 @@ }, "llama-index-agent-openai": { "hashes": [ - "sha256:13ce535f03e32c821763c01e26af4222f3981178622414d3868013a1946e8124", - "sha256:34be65011a508dd8cab0c9a606594f28075b98b0cebe69e3c543adc8564fee0d" + "sha256:d7f0fd4c87124781acd783be603871f8808b1a3969e876a9c96e2ed0844d46ac", + "sha256:debe86da6d9d983db32b445ddca7c798ac140fe59573bafded73595b3995f3d5" ], "markers": "python_version < '4.0' and python_full_version >= '3.8.1'", - "version": "==0.2.7" + "version": "==0.2.9" }, "llama-index-cli": { "hashes": [ @@ -805,19 +801,19 @@ }, "llama-index-embeddings-openai": { "hashes": [ - "sha256:1bc1fc9b46773a12870c5d3097d3735d7ca33805f12462a8e35ae8a6e5ce1cf6", - "sha256:c3cfa83b537ded34d035fc172a945dd444c87fb58a89b02dfbf785b675f9f681" + "sha256:6025e229e375201788a9b14d6ebe470329907576cba5f6b7b832c3d68f39db30", + "sha256:e20806fc4baff6b8f5274decf2c1ca7c5c737648e01865475ffada164e32e173" ], "markers": "python_version < '4.0' and python_full_version >= '3.8.1'", - "version": "==0.1.10" + "version": "==0.1.11" }, "llama-index-indices-managed-llama-cloud": { "hashes": [ - "sha256:30c73a77fc54fa83c4a183fcdc3b5138a6b709a6fefc9539d0cb0c6315b0f2fc", - "sha256:9a3db075878bc7adf798a74ec4d6220dec5421f46c0675702a94894934d17a7a" + "sha256:35ecc8c4d6098588e911b4bf990f8786765dab94fcd311729de2c2c7b3237bf3", + "sha256:8ee6b11e47f2c5e7302a749b6e2baf893ab602967d86b13c84455b8ee1fb1404" ], "markers": "python_version < '4.0' and python_full_version >= '3.8.1'", - "version": "==0.2.2" + "version": "==0.2.4" }, "llama-index-legacy": { "hashes": [ @@ -829,19 +825,19 @@ }, "llama-index-llms-openai": { "hashes": [ - "sha256:9031bd155c303f89cc51cfcc75d7d6f12fffa4274f2f9c7f67d4140350d13d56", - "sha256:c7b71cd34765e2d080d5eaf23c602877cc74fea162b59d53965273b2d4c4a56a" + "sha256:08a408cd53af4cd4623dd5807be4cbbd5e5b3ca01272128cd678d667343e4d5d", + "sha256:1ad8e4eb02f9410c2091749d4d9aa9db4452646b595eb5eb937edbc496fb65fe" ], "markers": "python_version < '4.0' and python_full_version >= '3.8.1'", - "version": "==0.1.24" + "version": "==0.1.26" }, "llama-index-multi-modal-llms-openai": { "hashes": [ - "sha256:0b6950a6cf98d16ade7d3b9dd0821ecfe457ca103819ae6c3e66cfc9634ca646", - "sha256:10de75a877a444af35306385faad9b9f0624391e55309970564114a080a0578c" + "sha256:16ae72ac3c5201ebd1d4b62203930c1768149ec85c3e477e5e51ed2ef8db1067", + "sha256:5e2c94a6415a2509cad035ccea34461959ae327a5900d3e820417e9ebb9a13ec" ], "markers": "python_version < '4.0' and python_full_version >= '3.8.1'", - "version": "==0.1.6" + "version": "==0.1.8" }, "llama-index-program-openai": { "hashes": [ @@ -861,27 +857,27 @@ }, "llama-index-readers-file": { "hashes": [ - "sha256:238ddd98aa377d6a44322013eb848056037c80ad84571ea5bf451a640fff4d5c", - "sha256:bc659e432d441c445e110580340675aa60abae1d82add4f65e559dfe8add541b" + "sha256:32f40465f2a8a65fa5773e03c9f4dd55164be934ae67fad62113680436787d91", + "sha256:d5f6cdd4685ee73103c68b9bc0dfb0d05439033133fc6bd45ef31ff41519e723" ], "markers": "python_version < '4.0' and python_full_version >= '3.8.1'", - "version": "==0.1.25" + "version": "==0.1.30" }, "llama-index-readers-llama-parse": { "hashes": [ - "sha256:78608b193c818894aefeee0aa303f02b7f80f2e4caf13866c2fd3b0b1023e2c0", - "sha256:c4914b37d12cceee56fbd185cca80f87d60acbf8ea7a73f9719610180be1fcdd" + "sha256:04f2dcfbb0fb87ce70890f5a2f4f89941d79be6a818b43738f053560e4b451cf", + "sha256:71d445a2357ce4c632e0fada7c913ac62790e77c062f12d916dd86378380ff1f" ], "markers": "python_version < '4.0' and python_full_version >= '3.8.1'", - "version": "==0.1.4" + "version": "==0.1.6" }, "llama-parse": { "hashes": [ - "sha256:08a48bcf4af5b623bf26fa6266038572b95409f7be64746067db8d38f6927fe5", - "sha256:a68fc91a2b0bce98a4960b8f709ca3c2f90b421da66e0d8522f0ea45b78846b9" + "sha256:657f8fa5f7d399f14c0454fc05cae6034da0373f191df6cfca17a1b4a704ef87", + "sha256:71974a57a73d642608cc406942bee4e7fc1a713fa410f51df67da509479ba544" ], "markers": "python_version < '4.0' and python_full_version >= '3.8.1'", - "version": "==0.4.5" + "version": "==0.4.9" }, "markdown-it-py": { "hashes": [ @@ -891,6 +887,14 @@ "markers": "python_version >= '3.8'", "version": "==3.0.0" }, + "markdownify": { + "hashes": [ + "sha256:1d181d43d20902bcc69d7be85b5316ed174d0dda72ff56e14ae4c95a4a407d22", + "sha256:ab257f9e6bd4075118828a28c9d02f8a4bfeb7421f558834aa79b2dfeb32a098" + ], + "index": "pypi", + "version": "==0.13.1" + }, "markupsafe": { "hashes": [ "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", @@ -1162,55 +1166,60 @@ }, "orjson": { "hashes": [ - "sha256:03b565c3b93f5d6e001db48b747d31ea3819b89abf041ee10ac6988886d18e01", - "sha256:099e81a5975237fda3100f918839af95f42f981447ba8f47adb7b6a3cdb078fa", - "sha256:10c0eb7e0c75e1e486c7563fe231b40fdd658a035ae125c6ba651ca3b07936f5", - "sha256:1146bf85ea37ac421594107195db8bc77104f74bc83e8ee21a2e58596bfb2f04", - "sha256:1670fe88b116c2745a3a30b0f099b699a02bb3482c2591514baf5433819e4f4d", - "sha256:185c394ef45b18b9a7d8e8f333606e2e8194a50c6e3c664215aae8cf42c5385e", - "sha256:1ad1de7fef79736dde8c3554e75361ec351158a906d747bd901a52a5c9c8d24b", - "sha256:235dadefb793ad12f7fa11e98a480db1f7c6469ff9e3da5e73c7809c700d746b", - "sha256:28afa96f496474ce60d3340fe8d9a263aa93ea01201cd2bad844c45cd21f5268", - "sha256:2d97531cdfe9bdd76d492e69800afd97e5930cb0da6a825646667b2c6c6c0211", - "sha256:338fd4f071b242f26e9ca802f443edc588fa4ab60bfa81f38beaedf42eda226c", - "sha256:36a10f43c5f3a55c2f680efe07aa93ef4a342d2960dd2b1b7ea2dd764fe4a37c", - "sha256:3d21b9983da032505f7050795e98b5d9eee0df903258951566ecc358f6696969", - "sha256:51bbcdea96cdefa4a9b4461e690c75ad4e33796530d182bdd5c38980202c134a", - "sha256:53ed1c879b10de56f35daf06dbc4a0d9a5db98f6ee853c2dbd3ee9d13e6f302f", - "sha256:545d493c1f560d5ccfc134803ceb8955a14c3fcb47bbb4b2fee0232646d0b932", - "sha256:584c902ec19ab7928fd5add1783c909094cc53f31ac7acfada817b0847975f26", - "sha256:5a35455cc0b0b3a1eaf67224035f5388591ec72b9b6136d66b49a553ce9eb1e6", - "sha256:5df58d206e78c40da118a8c14fc189207fffdcb1f21b3b4c9c0c18e839b5a214", - "sha256:64c9cc089f127e5875901ac05e5c25aa13cfa5dbbbd9602bda51e5c611d6e3e2", - "sha256:68f85ecae7af14a585a563ac741b0547a3f291de81cd1e20903e79f25170458f", - "sha256:6970ed7a3126cfed873c5d21ece1cd5d6f83ca6c9afb71bbae21a0b034588d96", - "sha256:6b68742c469745d0e6ca5724506858f75e2f1e5b59a4315861f9e2b1df77775a", - "sha256:7a5baef8a4284405d96c90c7c62b755e9ef1ada84c2406c24a9ebec86b89f46d", - "sha256:7d10cc1b594951522e35a3463da19e899abe6ca95f3c84c69e9e901e0bd93d38", - "sha256:85c89131d7b3218db1b24c4abecea92fd6c7f9fab87441cfc342d3acc725d807", - "sha256:8a11d459338f96a9aa7f232ba95679fc0c7cedbd1b990d736467894210205c09", - "sha256:8c13ca5e2ddded0ce6a927ea5a9f27cae77eee4c75547b4297252cb20c4d30e6", - "sha256:9cd684927af3e11b6e754df80b9ffafd9fb6adcaa9d3e8fdd5891be5a5cad51e", - "sha256:b2efbd67feff8c1f7728937c0d7f6ca8c25ec81373dc8db4ef394c1d93d13dc5", - "sha256:b39e006b00c57125ab974362e740c14a0c6a66ff695bff44615dcf4a70ce2b86", - "sha256:b6c8e30adfa52c025f042a87f450a6b9ea29649d828e0fec4858ed5e6caecf63", - "sha256:be79e2393679eda6a590638abda16d167754393f5d0850dcbca2d0c3735cebe2", - "sha256:c05f16701ab2a4ca146d0bca950af254cb7c02f3c01fca8efbbad82d23b3d9d4", - "sha256:c4057c3b511bb8aef605616bd3f1f002a697c7e4da6adf095ca5b84c0fd43595", - "sha256:c4a65310ccb5c9910c47b078ba78e2787cb3878cdded1702ac3d0da71ddc5228", - "sha256:ca0b3a94ac8d3886c9581b9f9de3ce858263865fdaa383fbc31c310b9eac07c9", - "sha256:cc28e90a7cae7fcba2493953cff61da5a52950e78dc2dacfe931a317ee3d8de7", - "sha256:cdf7365063e80899ae3a697def1277c17a7df7ccfc979990a403dfe77bb54d40", - "sha256:d69858c32f09c3e1ce44b617b3ebba1aba030e777000ebdf72b0d8e365d0b2b3", - "sha256:dbead71dbe65f959b7bd8cf91e0e11d5338033eba34c114f69078d59827ee139", - "sha256:dcbe82b35d1ac43b0d84072408330fd3295c2896973112d495e7234f7e3da2e1", - "sha256:dfc91d4720d48e2a709e9c368d5125b4b5899dced34b5400c3837dadc7d6271b", - "sha256:eded5138cc565a9d618e111c6d5c2547bbdd951114eb822f7f6309e04db0fb47", - "sha256:f4324929c2dd917598212bfd554757feca3e5e0fa60da08be11b4aa8b90013c1", - "sha256:fb66215277a230c456f9038d5e2d84778141643207f85336ef8d2a9da26bd7ca" + "sha256:03c95484d53ed8e479cade8628c9cea00fd9d67f5554764a1110e0d5aa2de96e", + "sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0", + "sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f", + "sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212", + "sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43", + "sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b", + "sha256:30b0a09a2014e621b1adf66a4f705f0809358350a757508ee80209b2d8dae219", + "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394", + "sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a", + "sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd", + "sha256:49e3bc615652617d463069f91b867a4458114c5b104e13b7ae6872e5f79d0844", + "sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5", + "sha256:5410111d7b6681d4b0d65e0f58a13be588d01b473822483f77f513c7f93bd3b2", + "sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b", + "sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143", + "sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38", + "sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5", + "sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148", + "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183", + "sha256:697a35a083c4f834807a6232b3e62c8b280f7a44ad0b759fd4dce748951e70db", + "sha256:6eeb13218c8cf34c61912e9df2de2853f1d009de0e46ea09ccdf3d757896af0a", + "sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e", + "sha256:738dbe3ef909c4b019d69afc19caf6b5ed0e2f1c786b5d6215fbb7539246e4c6", + "sha256:79b9b9e33bd4c517445a62b90ca0cc279b0f1f3970655c3df9e608bc3f91741a", + "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc", + "sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3", + "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34", + "sha256:965a916373382674e323c957d560b953d81d7a8603fbeee26f7b8248638bd48b", + "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365", + "sha256:a2debd8ddce948a8c0938c8c93ade191d2f4ba4649a54302a7da905a81f00b56", + "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5", + "sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863", + "sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba", + "sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed", + "sha256:bb1f28a137337fdc18384079fa5726810681055b32b92253fa15ae5656e1dddb", + "sha256:bf2fbbce5fe7cd1aa177ea3eab2b8e6a6bc6e8592e4279ed3db2d62e57c0e1b2", + "sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0", + "sha256:c2c116072a8533f2fec435fde4d134610f806bdac20188c7bd2081f3e9e0133f", + "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28", + "sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a", + "sha256:d40f839dddf6a7d77114fe6b8a70218556408c71d4d6e29413bb5f150a692ff7", + "sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c", + "sha256:e060748a04cccf1e0a6f2358dffea9c080b849a4a68c28b1b907f272b5127e9b", + "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7", + "sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb", + "sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0", + "sha256:f215789fb1667cdc874c1b8af6a84dc939fd802bf293a8334fce185c79cd359b", + "sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7", + "sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2", + "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7", + "sha256:fd502f96bf5ea9a61cbc0b2b5900d0dd68aa0da197179042bdd2be67e51a1e4b" ], "markers": "python_version >= '3.8'", - "version": "==3.10.5" + "version": "==3.10.6" }, "packaging": { "hashes": [ @@ -1490,11 +1499,11 @@ }, "pypdf": { "hashes": [ - "sha256:dc035581664e0ad717e3492acebc1a5fc23dba759e788e3d4a9fc9b1a32e72c1", - "sha256:fe63f3f7d1dcda1c9374421a94c1bba6c6f8c4a62173a59b64ffd52058f846b1" + "sha256:64b31da97eda0771ef22edb1bfecd5deee4b72c3d1736b7df2689805076d6418", + "sha256:b2f37fe9a3030aa97ca86067a56ba3f9d3565f9a791b305c7355d8392c30d91b" ], "markers": "python_version >= '3.6'", - "version": "==4.2.0" + "version": "==4.3.1" }, "python-dateutil": { "hashes": [ @@ -1697,11 +1706,11 @@ }, "setuptools": { "hashes": [ - "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05", - "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1" + "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936", + "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855" ], "markers": "python_version >= '3.12'", - "version": "==70.2.0" + "version": "==71.1.0" }, "shellingham": { "hashes": [ @@ -1810,11 +1819,11 @@ }, "tenacity": { "hashes": [ - "sha256:9e6f7cf7da729125c7437222f8a522279751cdfbe6b67bfe64f75d3a348661b2", - "sha256:cd80a53a79336edba8489e767f729e4f391c896956b57140b5d7511a64bbd3ef" + "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", + "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687" ], "markers": "python_version >= '3.8'", - "version": "==8.4.2" + "version": "==8.5.0" }, "tiktoken": { "hashes": [ @@ -1994,11 +2003,11 @@ "standard" ], "hashes": [ - "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81", - "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8" + "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81", + "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503" ], "markers": "python_version >= '3.8'", - "version": "==0.30.1" + "version": "==0.30.3" }, "uvloop": { "hashes": [ @@ -2432,27 +2441,27 @@ }, "botocore": { "hashes": [ - "sha256:7f7135178692b39143c8f152a618d2a3b71065a317569a7102d2306d4946f42f", - "sha256:c63fe9032091fb9e9477706a3ebfa4d0c109b807907051d892ed574f9b573e61" + "sha256:3fd4782362bd29c192704ebf859c5c8c5189ad05719e391eefe23088434427ae", + "sha256:849cb8e54e042443aeabcd7822b5f2b76cb5cfe33fe3a71f91c7c069748a869c" ], "markers": "python_version >= '3.8'", - "version": "==1.34.136" + "version": "==1.34.146" }, "botocore-stubs": { "hashes": [ - "sha256:b97bece44abc9f16c1fcb022d3f03c22d411ea3dcb4730207a318eb8e6ef63e7", - "sha256:f8a8716d9f1b8387acce6ef35d9428ed4ef1584dbeb6cce3f2f95ba6ff8120f7" + "sha256:a7d990c165e3151180a8c1c754183c2f687520117c918bdd6057f26baad4e1d2", + "sha256:f77385f2b6af64b04e00dffc0f684879f308c48ce05deefabbec46b290841fc1" ], "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.136" + "version": "==1.34.146" }, "certifi": { "hashes": [ - "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", - "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" + "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", + "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" ], "markers": "python_version >= '3.6'", - "version": "==2024.6.2" + "version": "==2024.7.4" }, "cffi": { "hashes": [ @@ -2618,40 +2627,35 @@ }, "cryptography": { "hashes": [ - "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad", - "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", - "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", - "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", - "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", - "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648", - "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", - "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", - "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c", - "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", - "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", - "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", - "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", - "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", - "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", - "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", - "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", - "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", - "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", - "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", - "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", - "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", - "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", - "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", - "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", - "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", - "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", - "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842", - "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", - "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", - "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", - "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e" - ], - "version": "==42.0.8" + "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709", + "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069", + "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2", + "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b", + "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e", + "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70", + "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778", + "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22", + "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895", + "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf", + "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431", + "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f", + "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947", + "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74", + "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc", + "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66", + "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66", + "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf", + "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f", + "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5", + "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e", + "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f", + "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55", + "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1", + "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47", + "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5", + "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0" + ], + "version": "==43.0.0" }, "docker": { "hashes": [ @@ -2867,11 +2871,20 @@ }, "types-awscrt": { "hashes": [ - "sha256:0beabdde0205dc1da679ea464fd3f98b570ef4f0fc825b155a974fb51b21e8d9", - "sha256:521ce54cc4dad9fe6480556bb0f8315a508106938ba1f2a0baccfcea7d4a4dee" + "sha256:0839fe12f0f914d8f7d63ed777c728cb4eccc2d5d79a26e377d12b0604e7bf0e", + "sha256:84a9f4f422ec525c314fdf54c23a1e73edfbcec968560943ca2d41cfae623b38" ], "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==0.20.12" + "version": "==0.21.2" + }, + "types-beautifulsoup4": { + "hashes": [ + "sha256:004f6096fdd83b19cdbf6cb10e4eae57b10205eccc365d0a69d77da836012e28", + "sha256:7ceda66a93ba28d759d5046d7fec9f4cad2f563a77b3a789efc90bcadafeefd1" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==4.12.0.20240511" }, "types-docker": { "hashes": [ @@ -2882,14 +2895,22 @@ "markers": "python_version >= '3.8'", "version": "==7.0.0.20240528" }, + "types-html5lib": { + "hashes": [ + "sha256:22736b7299e605ec4ba539d48691e905fd0c61c3ea610acc59922232dc84cede", + "sha256:af5de0125cb0fe5667543b158db83849b22e25c0e36c9149836b095548bf1020" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.11.20240228" + }, "types-requests": { "hashes": [ - "sha256:97bac6b54b5bd4cf91d407e62f0932a74821bc2211f22116d9ee1dd643826caf", - "sha256:ed5e8a412fcc39159d6319385c009d642845f250c63902718f605cd90faade31" + "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358", + "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.32.0.20240622" + "version": "==2.32.0.20240712" }, "types-s3transfer": { "hashes": [ diff --git a/opentrons-ai-server/api/data/python_api_219_docs.md b/opentrons-ai-server/api/data/python_api_219_docs.md new file mode 100644 index 00000000000..31d3ce47404 --- /dev/null +++ b/opentrons-ai-server/api/data/python_api_219_docs.md @@ -0,0 +1,6212 @@ +# [Python Protocol API v2](#) + +Python Protocol API + +### Table of Contents + +- [Welcome](index.html#document-index) +- [Tutorial](index.html#document-tutorial) +- [Versioning](index.html#document-versioning) +- [Labware](index.html#document-new_labware) +- [Moving Labware](index.html#document-moving_labware) +- [Hardware Modules](index.html#document-new_modules) +- [Deck Slots](index.html#document-deck_slots) +- [Pipettes](index.html#document-new_pipette) +- [Building Block Commands](index.html#document-new_atomic_commands) +- [Complex Commands](index.html#document-new_complex_commands) +- [Labware and Deck Positions](index.html#document-robot_position) +- [Runtime Parameters](index.html#document-runtime_parameters) +- [Advanced Control](index.html#document-new_advanced_running) +- [Protocol Examples](index.html#document-new_examples) +- [Adapting OT\-2 Protocols for Flex](index.html#document-adapting_ot2_flex) +- [API Version 2 Reference](index.html#document-new_protocol_api) + +--- + +- [OT\-2 Python API v1](../v1/index.html) + +### Related Topics + +- [Documentation overview](#) + +# Welcome + +## Tutorial + +### Introduction + +This tutorial will guide you through creating a Python protocol file from scratch. At the end of this process you’ll have a complete protocol that can run on a Flex or an OT\-2 robot. If you don’t have a Flex or an OT\-2 (or if you’re away from your lab, or if your robot is in use), you can use the same file to simulate the protocol on your computer instead. + +#### What You’ll Automate + +The lab task that you’ll automate in this tutorial is serial dilution: taking a solution and progressively diluting it by transferring it stepwise across a plate from column 1 to column 12\. With just a dozen or so lines of code, you can instruct your robot to perform the hundreds of individual pipetting actions necessary to fill an entire 96\-well plate. And all of those liquid transfers will be done automatically, so you’ll have more time to do other work in your lab. + +#### Before You Begin + +You’re going to write some Python code, but you don’t need to be a Python expert to get started writing Opentrons protocols. You should know some basic Python syntax, like how it uses [indentation](https://docs.python.org/3/reference/lexical_analysis.html#indentation) to group blocks of code, dot notation for [calling methods](https://docs.python.org/3/tutorial/classes.html#method-objects), and the format of [lists](https://docs.python.org/3/tutorial/introduction.html#lists) and [dictionaries](https://docs.python.org/3/tutorial/datastructures.html#dictionaries). You’ll also be using [common control structures](https://docs.python.org/3/tutorial/controlflow.html#if-statements) like `if` statements and `for` loops. + +You should write your code in your favorite plaintext editor or development environment and save it in a file with a `.py` extension, like `dilution-tutorial.py`. + +To simulate your code, you’ll need [Python 3\.10](https://www.python.org/downloads/) and the [pip package installer](https://pip.pypa.io/en/stable/getting-started/). Newer versions of Python aren’t yet supported by the Python Protocol API. If you don’t use Python 3\.10 as your system Python, we recommend using [pyenv](https://github.com/pyenv/pyenv) to manage multiple Python versions. + +#### Hardware and Labware + +Before running a protocol, you’ll want to have the right kind of hardware and labware ready for your Flex or OT\-2\. + +- **Flex users** should review Chapter 2: Installation and Relocation in the [instruction manual](https://insights.opentrons.com/hubfs/Products/Flex/Opentrons%20Flex%20Manual.pdf). Specifically, see the pipette information in the “Instrument Installation and Calibration” section. You can use either a 1\-channel or 8\-channel pipette for this tutorial. Most Flex code examples will use a [Flex 1\-Channel 1000 μL pipette](https://shop.opentrons.com/opentrons-flex-1-channel-pipette/). +- **OT\-2 users** should review the robot setup and pipette information on the [Get Started page](https://support.opentrons.com/s/ot2-get-started). Specifically, see [attaching pipettes](https://support.opentrons.com/s/article/Get-started-Attach-pipettes) and [initial calibration](https://support.opentrons.com/s/article/Get-started-Calibrate-the-deck). You can use either a single\-channel or 8\-channel pipette for this tutorial. Most OT\-2 code examples will use a [P300 Single\-Channel GEN2](https://shop.opentrons.com/single-channel-electronic-pipette-p20/) pipette. + +The Flex and OT\-2 use similar labware for serial dilution. The tutorial code will use the labware listed in the table below, but as long as you have labware of each type you can modify the code to run with your labware. + +| Labware type | Labware name | API load name | +| -------------- | ----------------------------------------------------------------------------------------------- | --------------------------------- | +| Reservoir | [NEST 12 Well Reservoir 15 mL](https://labware.opentrons.com/nest_12_reservoir_15ml) | `nest_12_reservoir_15ml` | +| Well plate | [NEST 96 Well Plate 200 µL Flat](https://labware.opentrons.com/nest_96_wellplate_200ul_flat) | `nest_96_wellplate_200ul_flat` | +| Flex tip rack | [Opentrons Flex Tips, 200 µL](https://shop.opentrons.com/opentrons-flex-tips-200-l/) | `opentrons_flex_96_tiprack_200ul` | +| OT\-2 tip rack | [Opentrons 96 Tip Rack](https://labware.opentrons.com/?category=tipRack&manufacturer=Opentrons) | `opentrons_96_tiprack_300ul` | + +For the liquids, you can use plain water as the diluent and water dyed with food coloring as the solution. + +### Create a Protocol File + +Let’s start from scratch to create your serial dilution protocol. Open up a new file in your editor and start with the line: + +``` +from opentrons import protocol_api + +``` + +Throughout this documentation, you’ll see protocols that begin with the `import` statement shown above. It identifies your code as an Opentrons protocol. This statement is not required, but including it is a good practice and allows most code editors to provide helpful autocomplete suggestions. + +Everything else in the protocol file is required. Next, you’ll specify the version of the API you’re using. Then comes the core of the protocol: defining a single `run()` function that provides the locations of your labware, states which kind of pipettes you’ll use, and finally issues the commands that the robot will perform. + +For this tutorial, you’ll write very little Python outside of the `run()` function. But for more complex applications it’s worth remembering that your protocol file _is_ a Python script, so any Python code that can run on your robot can be a part of a protocol. + +#### Metadata + +Every protocol needs to have a metadata dictionary with information about the protocol. At minimum, you need to specify what [version of the API](index.html#version-table) the protocol requires. The [scripts](https://github.com/Opentrons/opentrons/blob/edge/api/docs/v2/example_protocols/) for this tutorial were validated against API version 2\.16, so specify: + +``` +metadata = {"apiLevel": "2.16"} + +``` + +You can include any other information you like in the metadata dictionary. The fields `protocolName`, `description`, and `author` are all displayed in the Opentrons App, so it’s a good idea to expand the dictionary to include them: + +``` +metadata = { + "apiLevel": "2.16", + "protocolName": "Serial Dilution Tutorial", + "description": """This protocol is the outcome of following the + Python Protocol API Tutorial located at + https://docs.opentrons.com/v2/tutorial.html. It takes a + solution and progressively dilutes it by transferring it + stepwise across a plate.""", + "author": "New API User" + } + +``` + +Note, if you have a Flex, or are using an OT\-2 with API v2\.15 (or higher), we recommend adding a `requirements` section to your code. See the Requirements section below. + +#### Requirements + +The `requirements` code block can appear before _or_ after the `metadata` code block in a Python protocol. It uses the following syntax and accepts two arguments: `robotType` and `apiLevel`. + +Whether you need a `requirements` block depends on your robot model and API version. + +- **Flex:** The `requirements` block is always required. And, the API version does not go in the `metadata` section. The API version belongs in the `requirements`. For example: + +``` +requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +``` + +- **OT\-2:** The `requirements` block is optional, but including it is a recommended best practice, particularly if you’re using API version 2\.15 or greater. If you do use it, remember to remove the API version from the `metadata`. For example: + +``` +requirements = {"robotType": "OT-2", "apiLevel": "2.16"} + +``` + +With the metadata and requirements defined, you can move on to creating the `run()` function for your protocol. + +#### The `run()` function + +Now it’s time to actually instruct the Flex or OT\-2 how to perform serial dilution. All of this information is contained in a single Python function, which has to be named `run`. This function takes one argument, which is the _protocol context_. Many examples in these docs use the argument name `protocol`, and sometimes they specify the argument’s type: + +``` +def run(protocol: protocol_api.ProtocolContext): + +``` + +With the protocol context argument named and typed, you can start calling methods on `protocol` to add labware and hardware. + +##### Labware + +For serial dilution, you need to load a tip rack, reservoir, and 96\-well plate on the deck of your Flex or OT\-2\. Loading labware is done with the [`load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') method of the protocol context, which takes two arguments: the standard labware name as defined in the [Opentrons Labware Library](https://labware.opentrons.com/), and the position where you’ll place the labware on the robot’s deck. + +### Flex + +Here’s how to load the labware on a Flex in slots D1, D2, and D3 (repeating the `def` statement from above to show proper indenting): + +``` +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "D1") + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "D2") + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "D3") + +``` + +If you’re using a different model of labware, find its name in the Labware Library and replace it in your code. + +Now the robot will expect to find labware in a configuration that looks like this: + +### OT-2 + +Here’s how to load the labware on an OT\-2 in slots 1, 2, and 3 (repeating the `def` statement from above to show proper indenting): + +``` +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_96_tiprack_300ul", 1) + reservoir = protocol.load_labware("nest_12_reservoir_15ml", 2) + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", 3) + +``` + +If you’re using a different model of labware, find its name in the Labware Library and replace it in your code. + +Now the robot will expect to find labware in a configuration that looks like this: + +You may notice that these deck maps don’t show where the liquids will be at the start of the protocol. Liquid definitions aren’t required in Python protocols, unlike protocols made in [Protocol Designer](https://designer.opentrons.com/). If you want to identify liquids, see [Labeling Liquids in Wells](https://docs.opentrons.com/v2/new_labware.html#labeling-liquids-in-wells). (Sneak peek: you’ll put the diluent in column 1 of the reservoir and the solution in column 2 of the reservoir.) + +##### Trash Bin + +Flex and OT\-2 both come with a trash bin for disposing used tips. + +The OT\-2 trash bin is fixed in slot 12\. Since it can’t go anywhere else on the deck, you don’t need to write any code to tell the API where it is. Skip ahead to the Pipettes section below. + +Flex lets you put a [trash bin](index.html#configure-trash-bin) in multiple locations on the deck. You can even have more than one trash bin, or none at all (if you use the [waste chute](index.html#configure-waste-chute) instead, or if your protocol never trashes any tips). For serial dilution, you’ll need to dispose used tips, so you also need to tell the API where the trash container is located on your robot. Loading a trash bin on Flex is done with the [`load_trash_bin()`](index.html#opentrons.protocol_api.ProtocolContext.load_trash_bin 'opentrons.protocol_api.ProtocolContext.load_trash_bin') method, which takes one argument: its location. Here’s how to load the trash in slot A3: + +``` +trash = protocol.load_trash_bin("A3") + +``` + +##### Pipettes + +Next you’ll specify what pipette to use in the protocol. Loading a pipette is done with the [`load_instrument()`](index.html#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument') method, which takes three arguments: the name of the pipette, the mount it’s installed in, and the tip racks it should use when performing transfers. Load whatever pipette you have installed in your robot by using its [standard pipette name](index.html#new-pipette-models). Here’s how to load the pipette in the left mount and instantiate it as a variable named `left_pipette`: + +``` +# Flex +left_pipette = protocol.load_instrument("flex_1channel_1000", "left", tip_racks=[tips]) + +``` + +``` +# OT-2 +left_pipette = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips]) + +``` + +Since the pipette is so fundamental to the protocol, it might seem like you should have specified it first. But there’s a good reason why pipettes are loaded after labware: you need to have already loaded `tips` in order to tell the pipette to use it. And now you won’t have to reference `tips` again in your code — it’s assigned to the `left_pipette` and the robot will know to use it when commanded to pick up tips. + +Note + +You may notice that the value of `tip_racks` is in brackets, indicating that it’s a list. This serial dilution protocol only uses one tip rack, but some protocols require more tips, so you can assign them to a pipette all at once, like `tip_racks=[tips1, tips2]`. + +##### Commands + +Finally, all of your labware and hardware is in place, so it’s time to give the robot pipetting commands. The required steps of the serial dilution process break down into three main phases: + +1. Measure out equal amounts of diluent from the reservoir to every well on the plate. +2. Measure out equal amounts of solution from the reservoir into wells in the first column of the plate. +3. Move a portion of the combined liquid from column 1 to 2, then from column 2 to 3, and so on all the way to column 12\. + +Thanks to the flexibility of the API’s [`transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') method, which combines many [building block commands](index.html#v2-atomic-commands) into one call, each of these phases can be accomplished with a single line of code! You’ll just have to write a few more lines of code to repeat the process for as many rows as you want to fill. + +Let’s start with the diluent. This phase takes a larger quantity of liquid and spreads it equally to many wells. `transfer()` can handle this all at once, because it accepts either a single well or a list of wells for its source and destination: + +``` +left_pipette.transfer(100, reservoir["A1"], plate.wells()) + +``` + +Breaking down these single lines of code shows the power of [complex commands](index.html#v2-complex-commands). The first argument is the amount to transfer to each destination, 100 µL. The second argument is the source, column 1 of the reservoir (which is still specified with grid\-style coordinates as `A1` — a reservoir only has an A row). The third argument is the destination. Here, calling the [`wells()`](index.html#opentrons.protocol_api.Labware.wells 'opentrons.protocol_api.Labware.wells') method of `plate` returns a list of _every well_, and the command will apply to all of them. + +In plain English, you’ve instructed the robot, “For every well on the plate, aspirate 100 µL of fluid from column 1 of the reservoir and dispense it in the well.” That’s how we understand this line of code as scientists, yet the robot will understand and execute it as nearly 200 discrete actions. + +Now it’s time to start mixing in the solution. To do this row by row, nest the commands in a `for` loop: + +``` +for i in range(8): + row = plate.rows()[i] + +``` + +Using Python’s built\-in [`range`](https://docs.python.org/3/library/stdtypes.html#range '(in Python v3.12)') class is an easy way to repeat this block 8 times, once for each row. This also lets you use the repeat index `i` with `plate.rows()` to keep track of the current row. + +In each row, you first need to add solution. This will be similar to what you did with the diluent, but putting it only in column 1 of the plate. It’s best to mix the combined solution and diluent thoroughly, so add the optional `mix_after` argument to `transfer()`: + +``` +left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50)) + +``` + +As before, the first argument specifies to transfer 100 µL. The second argument is the source, column 2 of the reservoir. The third argument is the destination, the element at index 0 of the current `row`. Since Python lists are zero\-indexed, but columns on labware start numbering at 1, this will be well A1 on the first time through the loop, B1 the second time, and so on. The fourth argument specifies to mix 3 times with 50 µL of fluid each time. + +Finally, it’s time to dilute the solution down the row. One approach would be to nest another `for` loop here, but instead let’s use another feature of the `transfer()` method, taking lists as the source and destination arguments: + +``` +left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50)) + +``` + +There’s some Python shorthand here, so let’s unpack it. You can get a range of indices from a list using the colon `:` operator, and omitting it at either end means “from the beginning” or “until the end” of the list. So the source is `row[:11]`, from the beginning of the row until its 11th item. And the destination is `row[1:]`, from index 1 (column 2!) until the end. Since both of these lists have 11 items, `transfer()` will _step through them in parallel_, and they’re constructed so when the source is 0, the destination is 1; when the source is 1, the destination is 2; and so on. This condenses all of the subsequent transfers down the row into a single line of code. + +All that remains is for the loop to repeat these steps, filling each row down the plate. + +That’s it! If you’re using a single\-channel pipette, you’re ready to try out your protocol. + +##### 8\-Channel Pipette + +If you’re using an 8\-channel pipette, you’ll need to make a couple tweaks to the single\-channel code from above. Most importantly, whenever you target a well in row A of a plate with an 8\-channel pipette, it will move its topmost tip to row A, lining itself up over the entire column. + +Thus, when adding the diluent, instead of targeting every well on the plate, you should only target the top row: + +``` +left_pipette.transfer(100, reservoir["A1"], plate.rows()[0]) + +``` + +And by accessing an entire column at once, the 8\-channel pipette effectively implements the `for` loop in hardware, so you’ll need to remove it: + +``` +row = plate.rows()[0] +left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50)) +left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50)) + +``` + +Instead of tracking the current row in the `row` variable, this code sets it to always be row A (index 0\). + +### Try Your Protocol + +There are two ways to try out your protocol: simulation on your computer, or a live run on a Flex or OT\-2\. Even if you plan to run your protocol on a robot, it’s a good idea to check the simulation output first. + +If you get any errors in simulation, or you don’t get the outcome you expected when running your protocol, you can check your code against these reference protocols on GitHub: + +- [Flex: Single\-channel serial dilution](https://github.com/Opentrons/opentrons/blob/edge/api/docs/v2/example_protocols/dilution_tutorial_flex.py) +- [Flex: 8\-channel serial dilution](https://github.com/Opentrons/opentrons/blob/edge/api/docs/v2/example_protocols/dilution_tutorial_multi_flex.py) +- [OT\-2: Single\-channel serial dilution](https://github.com/Opentrons/opentrons/blob/edge/api/docs/v2/example_protocols/dilution_tutorial.py) +- [OT\-2: 8\-channel serial dilution](https://github.com/Opentrons/opentrons/blob/edge/api/docs/v2/example_protocols/dilution_tutorial_multi.py) + +#### In Simulation + +Simulation doesn’t require having a robot connected to your computer. You just need to install the [Opentrons Python module](https://pypi.org/project/opentrons/) using pip (`pip install opentrons`). This will give you access to the `opentrons_simulate` command\-line utility (`opentrons_simulate.exe` on Windows). + +To see a text preview of the steps your Flex or OT\-2 will take, use the change directory (`cd`) command to navigate to the location of your saved protocol file and run: + +``` +opentrons_simulate dilution-tutorial.py + +``` + +This should generate a lot of output! As written, the protocol has about 1000 steps. In fact, using a single\-channel pipette for serial dilution across the whole plate will take about half an hour — plenty of time to grab a coffee while your robot pipettes for you! ☕️ + +If that’s too long, you can always cancel your run partway through or modify `for i in range(8)` to loop through fewer rows. + +#### On a Robot + +The simplest way to run your protocol on a Flex or OT\-2 is to use the [Opentrons App](https://opentrons.com/ot-app). When you first launch the Opentrons App, you will see the Protocols screen. (Click **Protocols** in the left sidebar to access it at any other time.) Click **Import** in the top right corner to reveal the Import a Protocol pane. Then click **Choose File** and find your protocol in the system file picker, or drag and drop your protocol file into the well. + +You should see “Protocol \- Serial Dilution Tutorial” (or whatever `protocolName` you entered in the metadata) in the list of protocols. Click the three\-dot menu (⋮) for your protocol and choose **Start setup**. + +If you have any remaining calibration tasks to do, you can finish them up here. Below the calibration section is a preview of the initial deck state. Optionally you can run Labware Position Check, or you can go ahead and click **Proceed to Run**. + +On the Run tab, you can double\-check the Run Preview, which is similar to the command\-line simulation output. Make sure all your labware and liquids are in the right place, and then click **Start run**. The run log will update in real time as your robot proceeds through the steps. + +When it’s all done, check the results of your serial dilution procedure — you should have a beautiful dye gradient running across the plate! + +### Next Steps + +This tutorial has relied heavily on the `transfer()` method, but there’s much more that the Python Protocol API can do. Many advanced applications use [building block commands](index.html#v2-atomic-commands) for finer control over the robot. These commands let you aspirate and dispense separately, add air gaps, blow out excess liquid, move the pipette to any location, and more. For protocols that use [Opentrons hardware modules](index.html#new-modules), there are methods to control their behavior. And all of the API’s classes and methods are catalogued in the [API Reference](index.html#protocol-api-reference). + +## Versioning + +The Python Protocol API has its own versioning system, which is separate from the versioning system used for the robot software and the Opentrons App. This allows protocols to run on newer robot software versions without modification. + +### Major and Minor Versions + +The API uses a major and minor version number and does not use patch version numbers. For instance, major version 2 and minor version 0 is written as `2.0`. Versions are not decimal numbers, so `2.10` indicates major version 2 and minor version 10\. The Python Protocol API version will only increase based on changes that affect protocol behavior. + +The major version of the API increases whenever there are significant structural or behavioral changes to protocols. For instance, major version 2 of the API was introduced because it required protocols to have a `run` function that takes a `protocol` argument rather than importing the `robot`, `instruments`, and `labware` modules. Protocols written with major version 1 of the API will not run without modification in major version 2\. A similar level of structural change would require a major version 3\. This documentation only deals with features found in major version 2 of the API; see the [archived version 1 documentation](https://docs.opentrons.com/v1/index.html) for information on older protocols. + +The minor version of the API increases whenever there is new functionality that might change the way a protocol is written, or when a behavior changes in one aspect of the API but does not affect all protocols. For instance, adding support for a new hardware module, adding new parameters for a function, or deprecating a feature would increase the minor version of the API. + +### Specifying Versions + +You must specify the API version you are targeting in your Python protocol. In all minor versions, you can do this with the `apiLevel` key in the `metadata` dictionary, alongside any other metadata elements: + +``` + from opentrons import protocol_api + + metadata = { + "apiLevel": "2.19", + "author": "A. Biologist"} + + def run(protocol: protocol_api.ProtocolContext): + protocol.comment("Hello, world!") + +``` + +From version 2\.15 onward, you can specify `apiLevel` in the `requirements` dictionary instead: + +``` + from opentrons import protocol_api + + metadata = {"author": "A. Biologist"} + requirements = {"apiLevel": "2.19", "robotType": "Flex"} + + def run(protocol: protocol_api.ProtocolContext): + protocol.comment("Hello, Flex!") + +``` + +Choose only one of these places to specify `apiLevel`. If you put it in neither or both places, you will not be able to simulate or run your protocol. + +The version you specify determines the features and behaviors available to your protocol. For example, support for the Heater\-Shaker Module was added in version 2\.13, so you can’t specify a lower version and then call `HeaterShakerContext` methods without causing an error. This protects you from accidentally using features not present in your specified API version, and keeps your protocol portable between API versions. + +When choosing an API level, consider what features you need and how widely you plan to share your protocol. Throughout the Python Protocol API documentation, there are version statements indicating when elements (features, function calls, available properties, etc.) were introduced. Keep these in mind when specifying your protocol’s API version. Version statements look like this: + +New in version 2\.0\. + +On the one hand, using the highest available version will give your protocol access to all the latest [features and fixes](#version-notes). On the other hand, using the lowest possible version lets the protocol work on a wider range of robot software versions. For example, a protocol that uses the Heater\-Shaker and specifies version 2\.13 of the API should work equally well on a robot running version 6\.1\.0 or 6\.2\.0 of the robot software. Specifying version 2\.14 would limit the protocol to robots running 6\.2\.0 or higher. + +### Maximum Supported Versions + +The maximum supported API version for your robot is listed in the Opentrons App under **Robots** \> your robot \> **Robot Settings** \> **Advanced**. Before version 6\.0\.0 of the app, the same information was listed on your robot’s **Information** card. + +If you upload a protocol that specifies a higher API level than the maximum supported, your robot won’t be able to analyze or run your protocol. You can increase the maximum supported version by updating your robot software and Opentrons App. + +Opentrons robots running the latest software (7\.3\.0\) support the following version ranges: + +> - **Flex:** version 2\.15–2\.19\. +> - **OT\-2:** versions 2\.0–2\.19\. + +### API and Robot Software Versions + +This table lists the correspondence between Protocol API versions and robot software versions. + +| API Version | Introduced in Robot Software | +| ----------- | ---------------------------- | +| 2\.19 | 7\.3\.1 | +| 2\.18 | 7\.3\.0 | +| 2\.17 | 7\.2\.0 | +| 2\.16 | 7\.1\.0 | +| 2\.15 | 7\.0\.0 | +| 2\.14 | 6\.3\.0 | +| 2\.13 | 6\.1\.0 | +| 2\.12 | 5\.0\.0 | +| 2\.11 | 4\.4\.0 | +| 2\.10 | 4\.3\.0 | +| 2\.9 | 4\.1\.0 | +| 2\.8 | 4\.0\.0 | +| 2\.7 | 3\.21\.0 | +| 2\.6 | 3\.20\.0 | +| 2\.5 | 3\.19\.0 | +| 2\.4 | 3\.17\.1 | +| 2\.3 | 3\.17\.0 | +| 2\.2 | 3\.16\.0 | +| 2\.1 | 3\.15\.2 | +| 2\.0 | 3\.14\.0 | +| 1\.0 | 3\.0\.0 | + +### Changes in API Versions + +#### Version 2\.19 + +Opentrons recommends updating protocols from `apiLevel` 2\.18 to 2\.19 to take advantage of improved pipetting behavior. + +- This version uses new values for how much a tip overlaps with the pipette nozzle when the pipette picks up tips. This can correct errors caused by the robot positioning the tip slightly lower than intended, potentially making contact with labware. See [`pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') for additional details. + +#### Version 2\.18 + +- Define customizable parameters with the new `add_parameters()` function, and access their values on the [`ProtocolContext.params`](index.html#opentrons.protocol_api.ProtocolContext.params 'opentrons.protocol_api.ProtocolContext.params') object during a protocol run. See [Runtime Parameters](index.html#runtime-parameters) and related pages for more information. +- Move the pipette to positions relative to the top of a trash container. See [Position Relative to Trash Containers](index.html#position-relative-trash). The default behavior of [`drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip') also accounts for this new possibility. +- [`set_offset()`](index.html#opentrons.protocol_api.Labware.set_offset 'opentrons.protocol_api.Labware.set_offset') has been restored to the API with new behavior that applies to labware type–location pairs. +- Automatic tip tracking is now available for all nozzle configurations. + +#### Version 2\.17 + +- [`dispense()`](index.html#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') now raises an error if you try to dispense more than [`InstrumentContext.current_volume`](index.html#opentrons.protocol_api.InstrumentContext.current_volume 'opentrons.protocol_api.InstrumentContext.current_volume'). + +#### Version 2\.16 + +This version introduces new features for Flex and adds and improves methods for aspirating and dispensing. Note that when updating Flex protocols to version 2\.16, you _must_ load a trash container before dropping tips. + +- New features + + - Use [`configure_nozzle_layout()`](index.html#opentrons.protocol_api.InstrumentContext.configure_nozzle_layout 'opentrons.protocol_api.InstrumentContext.configure_nozzle_layout') to pick up a single column of tips with the 96\-channel pipette. See [Partial Tip Pickup](index.html#partial-tip-pickup). + - Specify the trash containers attached to your Flex with [`load_waste_chute()`](index.html#opentrons.protocol_api.ProtocolContext.load_waste_chute 'opentrons.protocol_api.ProtocolContext.load_waste_chute') and [`load_trash_bin()`](index.html#opentrons.protocol_api.ProtocolContext.load_trash_bin 'opentrons.protocol_api.ProtocolContext.load_trash_bin'). + - Dispense, blow out, drop tips, and dispose labware in the waste chute. Disposing labware requires the gripper and calling [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') with `use_gripper=True`. + - Perform actions in staging area slots by referencing slots A4 through D4\. See [Deck Slots](index.html#deck-slots). + - Explicitly command a pipette to [`prepare_to_aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate 'opentrons.protocol_api.InstrumentContext.prepare_to_aspirate'). The API usually prepares pipettes to aspirate automatically, but this is useful for certain applications, like pre\-wetting routines. + +- Improved features + + - [`aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate'), [`dispense()`](index.html#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense'), and [`mix()`](index.html#opentrons.protocol_api.InstrumentContext.mix 'opentrons.protocol_api.InstrumentContext.mix') will not move any liquid when called with `volume=0`. + +- Other changes + + - [`ProtocolContext.fixed_trash`](index.html#opentrons.protocol_api.ProtocolContext.fixed_trash 'opentrons.protocol_api.ProtocolContext.fixed_trash') and [`InstrumentContext.trash_container`](index.html#opentrons.protocol_api.InstrumentContext.trash_container 'opentrons.protocol_api.InstrumentContext.trash_container') now return [`TrashBin`](index.html#opentrons.protocol_api.TrashBin 'opentrons.protocol_api.TrashBin') objects instead of [`Labware`](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') objects. + - Flex will no longer automatically drop tips in the trash at the end of a protocol. You can add a [`drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip') command to your protocol or use the Opentrons App to drop the tips. + +#### Version 2\.15 + +This version introduces support for the Opentrons Flex robot, instruments, modules, and labware. + +- Flex features + + - Write protocols for Opentrons Flex by declaring `"robotType": "Flex"` in the new `requirements` dictionary. See the [examples in the Tutorial](index.html#tutorial-requirements). + - [`load_instrument()`](index.html#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument') supports loading Flex 1\-, 8\-, and 96\-channel pipettes. See [Loading Pipettes](index.html#new-create-pipette). + - The new [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') method can move labware automatically using the Flex Gripper. You can also move labware manually on Flex. + - [`load_module()`](index.html#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module') supports loading the [Magnetic Block](index.html#magnetic-block). + - The API does not enforce placement restrictions for the Heater\-Shaker module on Flex, because it is installed below\-deck in a module caddy. Pipetting restrictions are still in place when the Heater\-Shaker is shaking or its labware latch is open. + - The new [`configure_for_volume()`](index.html#opentrons.protocol_api.InstrumentContext.configure_for_volume 'opentrons.protocol_api.InstrumentContext.configure_for_volume') method can place Flex 50 µL pipettes in a low\-volume mode for dispensing very small volumes of liquid. See [Volume Modes](index.html#pipette-volume-modes). + +- Flex and OT\-2 features + + - Optionally specify `apiLevel` in the new `requirements` dictionary (otherwise, specify it in `metadata`). + - Optionally specify `"robotType": "OT-2"` in `requirements`. + - Use coordinates or numbers to specify [deck slots](index.html#deck-slots). These formats match physical labels on Flex and OT\-2, but you can use either system, regardless of `robotType`. + - The new module context `load_adapter()` methods let you load adapters and labware separately on modules, and [`ProtocolContext.load_adapter()`](index.html#opentrons.protocol_api.ProtocolContext.load_adapter 'opentrons.protocol_api.ProtocolContext.load_adapter') lets you load adapters directly in deck slots. See [Loading Labware on Adapters](index.html#labware-on-adapters). + - Move labware manually using [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware'), without having to stop your protocol. + - Manual labware moves support moving to or from the new [`OFF_DECK`](index.html#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK') location (outside of the robot). + - [`ProtocolContext.load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') also accepts [`OFF_DECK`](index.html#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK') as a location. This lets you prepare labware to be moved onto the deck later in a protocol. + - The new `push_out` parameter of the [`dispense()`](index.html#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') method helps ensure that the pipette dispenses all of its liquid when working with very small volumes. + - By default, repeated calls to [`drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip') cycle through multiple locations above the trash bin to prevent tips from stacking up. + +- Bug fixes + + - [`InstrumentContext.starting_tip`](index.html#opentrons.protocol_api.InstrumentContext.starting_tip 'opentrons.protocol_api.InstrumentContext.starting_tip') is now respected on the second and subsequent calls to [`InstrumentContext.pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') with no argument. + +#### Version 2\.14 + +This version introduces a new protocol runtime that offers more reliable run control +and builds a strong foundation for future Protocol API improvements. + +Several older parts of the Protocol API were deprecated as part of this switchover. +If you specify an API version of `2.13` or lower, your protocols will continue to execute on the old runtime. + +- Feature additions + + - [`ProtocolContext.define_liquid()`](index.html#opentrons.protocol_api.ProtocolContext.define_liquid 'opentrons.protocol_api.ProtocolContext.define_liquid') and [`Well.load_liquid()`](index.html#opentrons.protocol_api.Well.load_liquid 'opentrons.protocol_api.Well.load_liquid') added + to define different liquid types and add them to wells, respectively. + +- Bug fixes + + - [`Labware`](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') and [`Well`](index.html#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') now adhere to the protocol’s API level setting. + Prior to this version, they incorrectly ignored the setting. + - [`InstrumentContext.touch_tip()`](index.html#opentrons.protocol_api.InstrumentContext.touch_tip 'opentrons.protocol_api.InstrumentContext.touch_tip') will end with the pipette tip in the center of the well + instead of on the edge closest to the front of the machine. + - [`ProtocolContext.load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') now prefers loading user\-provided labware definitions + rather than built\-in definitions if no explicit `namespace` is specified. + - [`ProtocolContext.pause()`](index.html#opentrons.protocol_api.ProtocolContext.pause 'opentrons.protocol_api.ProtocolContext.pause') will now properly wait until you resume the protocol before moving on. + In previous versions, the run will not pause until the first call to a different `ProtocolContext` method. + - Motion planning has been improved to avoid certain erroneous downward movements, + especially when using [`InstrumentContext.aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate'). + - [`Labware.reset()`](index.html#opentrons.protocol_api.Labware.reset 'opentrons.protocol_api.Labware.reset') and [`Labware.tip_length`](index.html#opentrons.protocol_api.Labware.tip_length 'opentrons.protocol_api.Labware.tip_length') will raise useful errors if called on labware that is not a tip rack. + +- Removals + + - The `presses` and `increment` arguments of [`InstrumentContext.pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') were deprecated. + Configure your pipette pick\-up settings with the Opentrons App, instead. + - `InstrumentContext.speed` property was removed. + This property tried to allow setting a pipette’s **plunger** speed in mm/s. + However, it could only approximately set the plunger speed, + because the plunger’s speed is a stepwise function of the volume. + Use [`InstrumentContext.flow_rate`](index.html#opentrons.protocol_api.InstrumentContext.flow_rate 'opentrons.protocol_api.InstrumentContext.flow_rate') to set the flow rate in µL/s, instead. + - `load_labware_object()` was removed from module contexts as an unnecessary internal method. + - `geometry` was removed from module contexts in favor of + `model` and `type` attributes. + - `Well.geometry` was removed as unnecessary. + - `MagneticModuleContext.calibrate` was removed since it was never needed nor implemented. + - The `height` parameter of [`MagneticModuleContext.engage()`](index.html#opentrons.protocol_api.MagneticModuleContext.engage 'opentrons.protocol_api.MagneticModuleContext.engage') was removed. + Use `offset` or `height_from_base` instead. + - `Labware.separate_calibration` and [`Labware.set_calibration()`](index.html#opentrons.protocol_api.Labware.set_calibration 'opentrons.protocol_api.Labware.set_calibration') were removed, + since they were holdovers from a calibration system that no longer exists. + - Various methods and setters were removed that could modify tip state outside of + calls to [`InstrumentContext.pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') and [`InstrumentContext.drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip'). + This change allows the robot to track tip usage more completely and reliably. + You may still use [`Labware.reset()`](index.html#opentrons.protocol_api.Labware.reset 'opentrons.protocol_api.Labware.reset') and [`InstrumentContext.reset_tipracks()`](index.html#opentrons.protocol_api.InstrumentContext.reset_tipracks 'opentrons.protocol_api.InstrumentContext.reset_tipracks') + to reset your tip racks’ state. + + > - The [`Well.has_tip`](index.html#opentrons.protocol_api.Well.has_tip 'opentrons.protocol_api.Well.has_tip') **setter** was removed. **The getter is still supported.** + > - Internal methods `Labware.use_tips`, `Labware.previous_tip`, and `Labware.return_tips` + > were removed. + + - The `configuration` argument of [`ProtocolContext.load_module()`](index.html#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module') was removed + because it made unsafe modifications to the protocol’s geometry system, + and the Thermocycler’s “semi” configuration is not officially supported. + +- Known limitations + + - [`Labware.set_offset()`](index.html#opentrons.protocol_api.Labware.set_offset 'opentrons.protocol_api.Labware.set_offset') is not yet supported on this API version. + Run protocols via the Opentrons App, instead. + - [`ProtocolContext.max_speeds`](index.html#opentrons.protocol_api.ProtocolContext.max_speeds 'opentrons.protocol_api.ProtocolContext.max_speeds') is not yet supported on the API version. + Use [`InstrumentContext.default_speed`](index.html#opentrons.protocol_api.InstrumentContext.default_speed 'opentrons.protocol_api.InstrumentContext.default_speed') or the per\-method speed argument, instead. + - [`InstrumentContext.starting_tip`](index.html#opentrons.protocol_api.InstrumentContext.starting_tip 'opentrons.protocol_api.InstrumentContext.starting_tip') is not respected on the second and subsequent calls to [`InstrumentContext.pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') with no argument. + +#### Version 2\.13 + +- Adds [`HeaterShakerContext`](index.html#opentrons.protocol_api.HeaterShakerContext 'opentrons.protocol_api.HeaterShakerContext') to support the Heater\-Shaker Module. You can use the load name `heaterShakerModuleV1` with [`ProtocolContext.load_module()`](index.html#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module') to add a Heater\-Shaker to a protocol. +- [`InstrumentContext.drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip') now has a `prep_after` parameter. +- [`InstrumentContext.home()`](index.html#opentrons.protocol_api.InstrumentContext.home 'opentrons.protocol_api.InstrumentContext.home') may home _both_ pipettes as needed to avoid collision risks. +- [`InstrumentContext.aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') and [`InstrumentContext.dispense()`](index.html#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') will avoid interacting directly with modules. + +#### Version 2\.12 + +- [`ProtocolContext.resume()`](index.html#opentrons.protocol_api.ProtocolContext.resume 'opentrons.protocol_api.ProtocolContext.resume') has been deprecated. +- [`Labware.set_offset()`](index.html#opentrons.protocol_api.Labware.set_offset 'opentrons.protocol_api.Labware.set_offset') has been added to apply labware offsets to protocols run (exclusively) outside of the Opentrons App (Jupyter Notebook and SSH). + +#### Version 2\.11 + +- Attempting to aspirate from or dispense to tip racks will raise an error. + +#### Version 2\.10 + +- Moving to the same well twice in a row with different pipettes no longer results in strange diagonal movements. + +#### Version 2\.9 + +- You can now access certain geometry data regarding a labware’s well via a Well Object. See [Well Dimensions](index.html#new-labware-well-properties) for more information. + +#### Version 2\.8 + +- You can now pass in a list of volumes to distribute and consolidate. See [List of Volumes](index.html#distribute-consolidate-volume-list) for more information. + + - Passing in a zero volume to any [complex command](index.html#v2-complex-commands) will result in no actions taken for aspirate or dispense + +- [`Well.from_center_cartesian()`](index.html#opentrons.protocol_api.Well.from_center_cartesian 'opentrons.protocol_api.Well.from_center_cartesian') can be used to find a point within a well using normalized distance from the center in each axis. + + - Note that you will need to create a location object to use this function in a protocol. See [Labware](index.html#protocol-api-labware) for more information. + +- You can now pass in a blowout location to transfer, distribute, and consolidate + with the `blowout_location` parameter. See [`InstrumentContext.transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') for more detail! + +#### Version 2\.7 + +- Added `InstrumentContext.pair_with()`, an experimental feature for moving both pipettes simultaneously. + +Note + +This feature has been removed from the Python Protocol API. + +- Calling [`InstrumentContext.has_tip()`](index.html#opentrons.protocol_api.InstrumentContext.has_tip 'opentrons.protocol_api.InstrumentContext.has_tip') will return whether a particular instrument + has a tip attached or not. + +#### Version 2\.6 + +- GEN2 Single pipettes now default to flow rates equivalent to 10 mm/s plunger + speeds + + + Protocols that manually configure pipette flow rates will be unaffected + + For a comparison between API Versions, see [OT\-2 Pipette Flow Rates](index.html#ot2-flow-rates) + +#### Version 2\.5 + +- New [utility commands](index.html#new-utility-commands) were added: + + - [`ProtocolContext.set_rail_lights()`](index.html#opentrons.protocol_api.ProtocolContext.set_rail_lights 'opentrons.protocol_api.ProtocolContext.set_rail_lights'): turns robot rail lights on or off + - [`ProtocolContext.rail_lights_on`](index.html#opentrons.protocol_api.ProtocolContext.rail_lights_on 'opentrons.protocol_api.ProtocolContext.rail_lights_on'): describes whether or not the rail lights are on + - [`ProtocolContext.door_closed`](index.html#opentrons.protocol_api.ProtocolContext.door_closed 'opentrons.protocol_api.ProtocolContext.door_closed'): describes whether the robot door is closed + +#### Version 2\.4 + +- The following improvements were made to the `touch_tip` command: + + - The speed for `touch_tip` can now be lowered down to 1 mm/s + - `touch_tip` no longer moves diagonally from the X direction \-\> Y direction + - Takes into account geometry of the deck and modules + +#### Version 2\.3 + +- Magnetic Module GEN2 and Temperature Module GEN2 are now supported; you can load them with the names `"magnetic module gen2"` and `"temperature module gen2"`, respectively. +- All pipettes will return tips to tip racks from a higher position to avoid + possible collisions. +- During a [`mix()`](index.html#opentrons.protocol_api.InstrumentContext.mix 'opentrons.protocol_api.InstrumentContext.mix'), the pipette will no longer move up to clear the liquid in + between every dispense and following aspirate. +- You can now access the Temperature Module’s status via [`TemperatureModuleContext.status`](index.html#opentrons.protocol_api.TemperatureModuleContext.status 'opentrons.protocol_api.TemperatureModuleContext.status'). + +#### Version 2\.2 + +- You should now specify Magnetic Module engage height using the + `height_from_base` parameter, which specifies the height of the top of the + magnet from the base of the labware. For more, see [Engaging and Disengaging](index.html#magnetic-module-engage). +- Return tip will now use pre\-defined heights from hardware testing. For more information, see [Returning a Tip](index.html#pipette-return-tip). +- When using the return tip function, tips are no longer added back into the tip tracker. For more information, see [Returning a Tip](index.html#pipette-return-tip). + +#### Version 2\.1 + +- When loading labware onto a module, you can now specify a label with the `label` parameter of + [`MagneticModuleContext.load_labware()`](index.html#opentrons.protocol_api.MagneticModuleContext.load_labware 'opentrons.protocol_api.MagneticModuleContext.load_labware'), + [`TemperatureModuleContext.load_labware()`](index.html#opentrons.protocol_api.TemperatureModuleContext.load_labware 'opentrons.protocol_api.TemperatureModuleContext.load_labware'), or + [`ThermocyclerContext.load_labware()`](index.html#opentrons.protocol_api.ThermocyclerContext.load_labware 'opentrons.protocol_api.ThermocyclerContext.load_labware'), + just like you can when loading labware onto the deck with [`ProtocolContext.load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware'). + +#### Version 2\.0 + +Version 2 of the API is a new way to write Python protocols, with support for new modules like the Thermocycler. To transition your protocols from version 1 to version 2 of the API, follow this [migration guide](http://support.opentrons.com/en/articles/3425727-switching-your-protocols-from-api-version-1-to-version-2). + +We’ve also published a [more in\-depth discussion](http://support.opentrons.com/en/articles/3418212-opentrons-protocol-api-version-2) of why we developed version 2 of the API and how it differs from version 1\. + +## Labware + +Labware are the durable or consumable items that you work with, reuse, or discard while running a protocol on a Flex or OT\-2\. Items such as pipette tips, well plates, tubes, and reservoirs are all examples of labware. This section provides a brief overview of default labware, custom labware, and how to use basic labware API methods when creating a protocol for your robot. + +Note + +Code snippets use coordinate deck slot locations (e.g. `"D1"`, `"D2"`), like those found on Flex. If you have an OT\-2 and are using API version 2\.14 or earlier, replace the coordinate with its numeric OT\-2 equivalent. For example, slot D1 on Flex corresponds to slot 1 on an OT\-2\. See [Deck Slots](index.html#deck-slots) for more information. + +### Labware Types + +#### Default Labware + +Default labware is everything listed in the [Opentrons Labware Library](https://labware.opentrons.com/). When used in a protocol, your Flex or OT\-2 knows how to work with default labware. However, you must first inform the API about the labware you will place on the robot’s deck. Search the library when you’re looking for the API load names of the labware you want to use. You can copy the load names from the library and pass them to the [`load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') method in your protocol. + +#### Custom Labware + +Custom labware is labware that is not listed the Labware Library. If your protocol needs something that’s not in the library, you can create it with the [Opentrons Labware Creator](https://labware.opentrons.com/create/). However, before using the Labware Creator, you should take a moment to review the support article [Creating Custom Labware Definitions](https://support.opentrons.com/s/article/Creating-Custom-Labware-Definitions). + +After you’ve created your labware, save it as a `.json` file and add it to the Opentrons App. See [Using Labware in Your Protocols](https://support.opentrons.com/s/article/Using-labware-in-your-protocols) for instructions. + +If other people need to use your custom labware definition, they must also add it to their Opentrons App. + +### Loading Labware + +Throughout this section, we’ll use the labware listed in the following table. + +| Labware type | Labware name | API load name | +| -------------- | --------------------------------------------------------------------------------------------------- | --------------------------------- | +| Well plate | [Corning 96 Well Plate 360 µL Flat](https://labware.opentrons.com/corning_96_wellplate_360ul_flat/) | `corning_96_wellplate_360ul_flat` | +| Flex tip rack | [Opentrons Flex 96 Tips 200 µL](https://shop.opentrons.com/opentrons-flex-tips-200-l/) | `opentrons_flex_96_tiprack_200ul` | +| OT\-2 tip rack | [Opentrons 96 Tip Rack 300 µL](https://labware.opentrons.com/opentrons_96_tiprack_300ul) | `opentrons_96_tiprack_300ul` | + +Similar to the code sample in [How the API Works](index.html#overview-section-v2), here’s how you use the [`ProtocolContext.load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') method to load labware on either Flex or OT\-2\. + +``` +#Flex +tiprack = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "D1") +plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "D2") + +``` + +``` +#OT-2 +tiprack = protocol.load_labware("opentrons_96_tiprack_300ul", "1") +plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "2") + +``` + +New in version 2\.0\. + +When the `load_labware` method loads labware into your protocol, it returns a [`Labware`](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware') object. + +Tip + +The `load_labware` method includes an optional `label` argument. You can use it to identify labware with a descriptive name. If used, the label value is displayed in the Opentrons App. For example: + +``` +tiprack = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location="D1", + label="any-name-you-want") + +``` + +#### Loading Labware on Adapters + +The previous section demonstrates loading labware directly into a deck slot. But you can also load labware on top of an adapter that either fits on a module or goes directly on the deck. The ability to combine labware with adapters adds functionality and flexibility to your robot and protocols. + +You can either load the adapter first and the labware second, or load both the adapter and labware all at once. + +##### Loading Separately + +The `load_adapter()` method is available on `ProtocolContext` and module contexts. It behaves similarly to `load_labware()`, requiring the load name and location for the desired adapter. Load a module, adapter, and labware with separate calls to specify each layer of the physical stack of components individually: + +``` +hs_mod = protocol.load_module("heaterShakerModuleV1", "D1") +hs_adapter = hs_mod.load_adapter("opentrons_96_flat_bottom_adapter") +hs_plate = hs_adapter.load_labware("nest_96_wellplate_200ul_flat") + +``` + +New in version 2\.15: The `load_adapter()` method. + +##### Loading Together + +Use the `adapter` argument of `load_labware()` to load an adapter at the same time as labware. For example, to load the same 96\-well plate and adapter from the previous section at once: + +``` +hs_plate = hs_mod.load_labware( + name="nest_96_wellplate_200ul_flat", + adapter="opentrons_96_flat_bottom_adapter" +) + +``` + +New in version 2\.15: The `adapter` parameter. + +The API also has some “combination” labware definitions, which treat the adapter and labware as a unit: + +``` +hs_combo = hs_mod.load_labware( + "opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat" +) + +``` + +Loading labware this way prevents you from [moving the labware](index.html#moving-labware) onto or off of the adapter, so it’s less flexible than loading the two separately. Avoid using combination definitions unless your protocol specifies an `apiLevel` of 2\.14 or lower. + +### Accessing Wells in Labware + +#### Well Ordering + +You need to select which wells to transfer liquids to and from over the course of a protocol. + +Rows of wells on a labware have labels that are capital letters starting with A. For instance, an 96\-well plate has 8 rows, labeled `"A"` through `"H"`. + +Columns of wells on a labware have labels that are numbers starting with 1\. For instance, a 96\-well plate has columns `"1"` through `"12"`. + +All well\-accessing functions start with the well at the top left corner of the labware. The ending well is in the bottom right. The order of travel from top left to bottom right depends on which function you use. + +The code in this section assumes that `plate` is a 24\-well plate. For example: + +``` +plate = protocol.load_labware("corning_24_wellplate_3.4ml_flat", location="D1") + +``` + +#### Accessor Methods + +The API provides many different ways to access wells inside labware. Different methods are useful in different contexts. The table below lists out the methods available to access wells and their differences. + +| Method | Returns | Example | +| ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------- | +| [`Labware.wells()`](index.html#opentrons.protocol_api.Labware.wells 'opentrons.protocol_api.Labware.wells') | List of all wells. | `[labware:A1, labware:B1, labware:C1...]` | +| [`Labware.rows()`](index.html#opentrons.protocol_api.Labware.rows 'opentrons.protocol_api.Labware.rows') | List of lists grouped by row. | `[[labware:A1, labware:A2...], [labware:B1, labware:B2...]]` | +| [`Labware.columns()`](index.html#opentrons.protocol_api.Labware.columns 'opentrons.protocol_api.Labware.columns') | List of lists grouped by column. | `[[labware:A1, labware:B1...], [labware:A2, labware:B2...]]` | +| [`Labware.wells_by_name()`](index.html#opentrons.protocol_api.Labware.wells_by_name 'opentrons.protocol_api.Labware.wells_by_name') | Dictionary with well names as keys. | `{"A1": labware:A1, "B1": labware:B1}` | +| [`Labware.rows_by_name()`](index.html#opentrons.protocol_api.Labware.rows_by_name 'opentrons.protocol_api.Labware.rows_by_name') | Dictionary with row names as keys. | `{"A": [labware:A1, labware:A2...], "B": [labware:B1, labware:B2...]}` | +| [`Labware.columns_by_name()`](index.html#opentrons.protocol_api.Labware.columns_by_name 'opentrons.protocol_api.Labware.columns_by_name') | Dictionary with column names as keys. | `{"1": [labware:A1, labware:B1...], "2": [labware:A2, labware:B2...]}` | + +#### Accessing Individual Wells + +##### Dictionary Access + +The simplest way to refer to a single well is by its [`well_name`](index.html#opentrons.protocol_api.Well.well_name 'opentrons.protocol_api.Well.well_name'), like A1 or D6\. Referencing a particular key in the result of [`Labware.wells_by_name()`](index.html#opentrons.protocol_api.Labware.wells_by_name 'opentrons.protocol_api.Labware.wells_by_name') accomplishes this. This is such a common task that the API also has an equivalent shortcut: dictionary indexing. + +``` +a1 = plate.wells_by_name()["A1"] +d6 = plate["D6"] # dictionary indexing + +``` + +If a well does not exist in the labware, such as `plate["H12"]` on a 24\-well plate, the API will raise a `KeyError`. In contrast, it would be a valid reference on a standard 96\-well plate. + +New in version 2\.0\. + +##### List Access From `wells` + +In addition to referencing wells by name, you can also reference them with zero\-indexing. The first well in a labware is at position 0\. + +``` +plate.wells()[0] # well A1 +plate.wells()[23] # well D6 + +``` + +Tip + +You may find coordinate well names like `"B3"` easier to reason with, especially when working with irregular labware, e.g. +`opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical` (see the [Opentrons 10 Tube Rack](https://labware.opentrons.com/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical) in the Labware Library). Whichever well access method you use, your protocol will be most maintainable if you use only one access method consistently. + +New in version 2\.0\. + +#### Accessing Groups of Wells + +When handling liquid, you can provide a group of wells as the source or destination. Alternatively, you can take a group of wells and loop (or iterate) through them, with each liquid\-handling command inside the loop accessing the loop index. + +Use [`Labware.rows_by_name()`](index.html#opentrons.protocol_api.Labware.rows_by_name 'opentrons.protocol_api.Labware.rows_by_name') to access a specific row of wells or [`Labware.columns_by_name()`](index.html#opentrons.protocol_api.Labware.columns_by_name 'opentrons.protocol_api.Labware.columns_by_name') to access a specific column of wells on a labware. These methods both return a dictionary with the row or column name as the keys: + +``` +row_dict = plate.rows_by_name()["A"] +row_list = plate.rows()[0] # equivalent to the line above +column_dict = plate.columns_by_name()["1"] +column_list = plate.columns()[0] # equivalent to the line above + +print('Column "1" has', len(column_dict), 'wells') # Column "1" has 4 wells +print('Row "A" has', len(row_dict), 'wells') # Row "A" has 6 wells + +``` + +Since these methods return either lists or dictionaries, you can iterate through them as you would regular Python data structures. + +For example, to transfer 50 µL of liquid from the first well of a reservoir to each of the wells of row `"A"` on a plate: + +``` +for well in plate.rows()[0]: + pipette.transfer(reservoir["A1"], well, 50) + +``` + +Equivalently, using `rows_by_name`: + +``` +for well in plate.rows_by_name()["A"].values(): + pipette.transfer(reservoir["A1"], well, 50) + +``` + +New in version 2\.0\. + +### Labeling Liquids in Wells + +Optionally, you can specify the liquids that should be in various wells at the beginning of your protocol. Doing so helps you identify well contents by name and volume, and adds corresponding labels to a single well, or group of wells, in well plates and reservoirs. You can view the initial liquid setup: + +- For Flex protocols, on the touchscreen. +- For Flex or OT\-2 protocols, in the Opentrons App (v6\.3\.0 or higher). + +To use these optional methods, first create a liquid object with [`ProtocolContext.define_liquid()`](index.html#opentrons.protocol_api.ProtocolContext.define_liquid 'opentrons.protocol_api.ProtocolContext.define_liquid') and then label individual wells by calling [`Well.load_liquid()`](index.html#opentrons.protocol_api.Well.load_liquid 'opentrons.protocol_api.Well.load_liquid'). + +Let’s examine how these two methods work. The following examples demonstrate how to define colored water samples for a well plate and reservoir. + +#### Defining Liquids + +This example uses `define_liquid` to create two liquid objects and instantiates them with the variables `greenWater` and `blueWater`, respectively. The arguments for `define_liquid` are all required, and let you name the liquid, describe it, and assign it a color: + +``` +greenWater = protocol.define_liquid( + name="Green water", + description="Green colored water for demo", + display_color="#00FF00", +) +blueWater = protocol.define_liquid( + name="Blue water", + description="Blue colored water for demo", + display_color="#0000FF", +) + +``` + +New in version 2\.14\. + +The `display_color` parameter accepts a hex color code, which adds a color to that liquid’s label when you import your protocol into the Opentrons App. The `define_liquid` method accepts standard 3\-, 4\-, 6\-, and 8\-character hex color codes. + +#### Labeling Wells and Reservoirs + +This example uses `load_liquid` to label the initial well location, contents, and volume (in µL) for the liquid objects created by `define_liquid`. Notice how values of the `liquid` argument use the variable names `greenWater` and `blueWater` (defined above) to associate each well with a particular liquid: + +``` +well_plate["A1"].load_liquid(liquid=greenWater, volume=50) +well_plate["A2"].load_liquid(liquid=greenWater, volume=50) +well_plate["B1"].load_liquid(liquid=blueWater, volume=50) +well_plate["B2"].load_liquid(liquid=blueWater, volume=50) +reservoir["A1"].load_liquid(liquid=greenWater, volume=200) +reservoir["A2"].load_liquid(liquid=blueWater, volume=200) + +``` + +New in version 2\.14\. + +This information is available after you import your protocol to the app or send it to Flex. A summary of liquids appears on the protocol detail page, and well\-by\-well detail is available on the run setup page (under Initial Liquid Setup in the app, or under Liquids on Flex). + +Note + +`load_liquid` does not validate volume for your labware nor does it prevent you from adding multiple liquids to each well. For example, you could label a 40 µL well with `greenWater`, `volume=50`, and then also add blue water to the well. The API won’t stop you. It’s your responsibility to ensure the labels you use accurately reflect the amounts and types of liquid you plan to place into wells and reservoirs. + +#### Labeling vs Handling Liquids + +The `load_liquid` arguments include a volume amount (`volume=n` in µL). This amount is just a label. It isn’t a command or function that manipulates liquids. It only tells you how much liquid should be in a well at the start of the protocol. You need to use a method like [`transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') to physically move liquids from a source to a destination. + +### Well Dimensions + +The functions in the [Accessing Wells in Labware](#new-well-access) section above return a single [`Well`](index.html#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') object or a larger object representing many wells. [`Well`](index.html#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') objects have attributes that provide information about their physical shape, such as the depth or diameter, as specified in their corresponding labware definition. These properties can be used for different applications, such as calculating the volume of a well or a [position relative to the well](index.html#position-relative-labware). + +#### Depth + +Use [`Well.depth`](index.html#opentrons.protocol_api.Well.depth 'opentrons.protocol_api.Well.depth') to get the distance in mm between the very top of the well and the very bottom. For example, a conical well’s depth is measured from the top center to the bottom center of the well. + +``` +plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "D1") +depth = plate["A1"].depth # 10.67 + +``` + +#### Diameter + +Use [`Well.diameter`](index.html#opentrons.protocol_api.Well.diameter 'opentrons.protocol_api.Well.diameter') to get the diameter of a given well in mm. Since diameter is a circular measurement, this attribute is only present on labware with circular wells. If the well is not circular, the value will be `None`. Use length and width (see below) for non\-circular wells. + +``` +plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "D1") +diameter = plate["A1"].diameter # 6.86 + +``` + +#### Length + +Use [`Well.length`](index.html#opentrons.protocol_api.Well.length 'opentrons.protocol_api.Well.length') to get the length of a given well in mm. Length is defined as the distance along the robot’s x\-axis (left to right). This attribute is only present on rectangular wells. If the well is not rectangular, the value will be `None`. Use diameter (see above) for circular wells. + +``` +plate = protocol.load_labware("nest_12_reservoir_15ml", "D1") +length = plate["A1"].length # 8.2 + +``` + +#### Width + +Use [`Well.width`](index.html#opentrons.protocol_api.Well.width 'opentrons.protocol_api.Well.width') to get the width of a given well in mm. Width is defined as the distance along the y\-axis (front to back). This attribute is only present on rectangular wells. If the well is not rectangular, the value will be `None`. Use diameter (see above) for circular wells. + +``` +plate = protocol.load_labware("nest_12_reservoir_15ml", "D1") +width = plate["A1"].width # 71.2 + +``` + +New in version 2\.9\. + +## Moving Labware + +You can move an entire labware (and all of its contents) from one deck slot to another at any point during your protocol. On Flex, you can either use the gripper or move the labware manually. On OT\-2, you can can only move labware manually, since it doesn’t have a gripper instrument. + +### Basic Movement + +Use the [`ProtocolContext.move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') method to initiate a move, regardless of whether it uses the gripper. + +``` +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "D1") + protocol.move_labware(labware=plate, new_location="D2") + +``` + +New in version 2\.15\. + +The required arguments of `move_labware()` are the `labware` you want to move and its `new_location`. You don’t need to specify where the move begins, since that information is already stored in the [`Labware`](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware') object — `plate` in this example. The destination of the move can be any empty deck slot, or a module that’s ready to have labware added to it (see [Movement with Modules](#movement-modules) below). Movement to an occupied location, including the labware’s current location, will raise an error. + +When the move step is complete, the API updates the labware’s location, so you can move the plate multiple times: + +``` +protocol.move_labware(labware=plate, new_location="D2") +protocol.move_labware(labware=plate, new_location="D3") + +``` + +For the first move, the API knows to find the plate in its initial load location, slot D1\. For the second move, the API knows to find the plate in D2\. + +### Automatic vs Manual Moves + +There are two ways to move labware: + +- Automatically, with the Opentrons Flex Gripper. +- Manually, by pausing the protocol until a user confirms that they’ve moved the labware. + +The `use_gripper` parameter of [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') determines whether a movement is automatic or manual. Set its value to `True` for an automatic move. The default value is `False`, so if you don’t specify a value, the protocol will pause for a manual move. + +``` +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "D1") + + # have the gripper move the plate from D1 to D2 + protocol.move_labware(labware=plate, new_location="D2", use_gripper=True) + + # pause to move the plate manually from D2 to D3 + protocol.move_labware(labware=plate, new_location="D3", use_gripper=False) + + # pause to move the plate manually from D3 to C1 + protocol.move_labware(labware=plate, new_location="C1") + +``` + +New in version 2\.15\. + +Note + +Don’t add a `pause()` command before `move_labware()`. When `use_gripper` is unset or `False`, the protocol pauses when it reaches the movement step. The Opentrons App or the touchscreen on Flex shows an animation of the labware movement that you need to perform manually. The protocol only resumes when you press **Confirm and resume**. + +The above example is a complete and valid `run()` function. You don’t have to load the gripper as an instrument, and there is no `InstrumentContext` for the gripper. All you have to do to specify that a protocol requires the gripper is to include at least one `move_labware()` command with `use_gripper=True`. + +If you attempt to use the gripper to move labware in an OT\-2 protocol, the API will raise an error. + +### Supported Labware + +You can manually move any standard or custom labware. Using the gripper to move the following labware is fully supported by Opentrons: + +| Labware Type | API Load Names | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Full\-skirt PCR plates | _ `armadillo_96_wellplate_200ul_pcr_full_skirt` _ `opentrons_96_wellplate_200ul_pcr_full_skirt` | +| NEST well plates | _ `nest_96_wellplate_200ul_flat` _ `nest_96_wellplate_2ml_deep` | +| Opentrons Flex 96 Tip Racks | _ `opentrons_flex_96_tiprack_50ul` _ `opentrons_flex_96_tiprack_200ul` _ `opentrons_flex_96_tiprack_1000ul` _ `opentrons_flex_96_filtertiprack_50ul` _ `opentrons_flex_96_filtertiprack_200ul` _ `opentrons_flex_96_filtertiprack_1000ul` | + +The gripper may work with other ANSI/SLAS standard labware, but this is not recommended. + +Note + +The labware definitions listed above include information about the position and force that the gripper uses to pick up the labware. The gripper uses default values for labware definitions that don’t include position and force information. The Python Protocol API won’t raise a warning or error if you try to grip and move other types of labware. + +### Movement with Modules + +Moving labware on and off of modules lets you precisely control when the labware is in contact with the hot, cold, or magnetic surfaces of the modules — all within a single protocol. + +When moving labware anywhere that isn’t an empty deck slot, consider what physical object the labware will rest on following the move. That object should be the value of `new_location`, and you need to make sure it’s already loaded before the move. For example, if you want to move a 96\-well flat plate onto a Heater\-Shaker module, you actually want to have it rest on top of the Heater\-Shaker’s 96 Flat Bottom Adapter. Pass the adapter, not the module or the slot, as the value of `new_location`: + +``` +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "D1") + hs_mod = protocol.load_module("heaterShakerModuleV1", "C1") + hs_adapter = hs_mod.load_adapter("opentrons_96_flat_bottom_adapter") + hs_mod.open_labware_latch() + protocol.move_labware( + labware=plate, new_location=hs_adapter, use_gripper=True + ) + +``` + +New in version 2\.15\. + +If you try to move the plate to slot C1 or the Heater\-Shaker module, the API will raise an error, because C1 is occupied by the Heater\-Shaker, and the Heater\-Shaker is occupied by the adapter. Only the adapter, as the topmost item in that stack, is unoccupied. + +Also note the `hs_mod.open_labware_latch()` command in the above example. To move labware onto or off of a module, you have to make sure that it’s physically accessible: + +> - For the Heater\-Shaker, use [`open_labware_latch()`](index.html#opentrons.protocol_api.HeaterShakerContext.open_labware_latch 'opentrons.protocol_api.HeaterShakerContext.open_labware_latch'). +> - For the Thermocycler, use [`open_lid()`](index.html#opentrons.protocol_api.ThermocyclerContext.open_lid 'opentrons.protocol_api.ThermocyclerContext.open_lid'). + +If the labware is inaccessible, the API will raise an error. + +### Movement into the Waste Chute + +Move used tip racks and well plates to the waste chute to dispose of them. This requires you to first [configure the waste chute](index.html#configure-waste-chute) in your protocol. Then use the loaded [`WasteChute`](index.html#opentrons.protocol_api.WasteChute 'opentrons.protocol_api.WasteChute') object as the value of `new_location`: + +``` +chute = protocol.load_waste_chute() +protocol.move_labware( + labware=plate, new_location=chute, use_gripper=True +) + +``` + +New in version 2\.16\. + +This will pick up `plate` from its current location and drop it into the chute. + +Always specify `use_gripper=True` when moving labware into the waste chute. The chute is not designed for manual movement. You can still manually move labware to other locations, including off\-deck, with the chute installed. + +### The Off\-Deck Location + +In addition to moving labware around the deck, [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') can also prompt you to move labware off of or onto the deck. + +Remove labware from the deck to perform tasks like retrieving samples or discarding a spent tip rack. The destination location for such moves is the special constant [`OFF_DECK`](index.html#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK'): + +``` +protocol.move_labware(labware=plate, new_location=protocol_api.OFF_DECK) + +``` + +New in version 2\.15\. + +Moving labware off\-deck always requires user intervention, because the gripper can’t reach outside of the robot. Omit the `use_gripper` parameter or explicitly set it to `False`. If you try to move labware off\-deck with `use_gripper=True`, the API will raise an error. + +You can also load labware off\-deck, in preparation for a `move_labware()` command that brings it _onto_ the deck. For example, you could assign two tip racks to a pipette — one on\-deck, and one off\-deck — and then swap out the first rack for the second one: + +> ``` +> from opentrons import protocol_api +> +> metadata = {"apiLevel": "2.19", "protocolName": "Tip rack replacement"} +> requirements = {"robotType": "OT-2"} +> +> +> def run(protocol: protocol_api.ProtocolContext): +> tips1 = protocol.load_labware("opentrons_96_tiprack_1000ul", 1) +> # load another tip rack but don't put it in a slot yet +> tips2 = protocol.load_labware( +> "opentrons_96_tiprack_1000ul", protocol_api.OFF_DECK +> ) +> pipette = protocol.load_instrument( +> "p1000_single_gen2", "left", tip_racks=[tips1, tips2] +> ) +> # use all the on-deck tips +> for i in range(96): +> pipette.pick_up_tip() +> pipette.drop_tip() +> # pause to move the spent tip rack off-deck +> protocol.move_labware(labware=tips1, new_location=protocol_api.OFF_DECK) +> # pause to move the fresh tip rack on-deck +> protocol.move_labware(labware=tips2, new_location=1) +> pipette.pick_up_tip() +> +> ``` + +Using the off\-deck location to remove or replace labware lets you continue your workflow in a single protocol, rather than needing to end a protocol, reset the deck, and start a new protocol run. + +## Hardware Modules + +### Module Setup + +#### Loading Modules onto the Deck + +Similar to labware and pipettes, you must inform the API about the modules you want to use in your protocol. Even if you don’t use the module anywhere else in your protocol, the Opentrons App and the robot won’t let you start the protocol run until all loaded modules that use power are connected via USB and turned on. + +Use [`ProtocolContext.load_module()`](index.html#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module') to load a module. + +### Flex + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # Load a Heater-Shaker Module GEN1 in deck slot D1. + heater_shaker = protocol.load_module( + module_name="heaterShakerModuleV1", location="D1") + + # Load a Temperature Module GEN2 in deck slot D3. + temperature_module = protocol.load_module( + module_name="temperature module gen2", location="D3") + +``` + +After the `load_module()` method loads the modules into your protocol, it returns the [`HeaterShakerContext`](index.html#opentrons.protocol_api.HeaterShakerContext 'opentrons.protocol_api.HeaterShakerContext') and [`TemperatureModuleContext`](index.html#opentrons.protocol_api.TemperatureModuleContext 'opentrons.protocol_api.TemperatureModuleContext') objects. + +### OT-2 + +``` +from opentrons import protocol_api + +metadata = {"apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # Load a Magnetic Module GEN2 in deck slot 1. + magnetic_module = protocol.load_module( + module_name="magnetic module gen2", location=1) + + # Load a Temperature Module GEN1 in deck slot 3. + temperature_module = protocol.load_module( + module_name="temperature module", location=3) + +``` + +After the `load_module()` method loads the modules into your protocol, it returns the [`MagneticModuleContext`](index.html#opentrons.protocol_api.MagneticModuleContext 'opentrons.protocol_api.MagneticModuleContext') and [`TemperatureModuleContext`](index.html#opentrons.protocol_api.TemperatureModuleContext 'opentrons.protocol_api.TemperatureModuleContext') objects. + +New in version 2\.0\. + +##### Available Modules + +The first parameter of [`ProtocolContext.load_module()`](index.html#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module') is the module’s _API load name_. The load name tells your robot which module you’re going to use in a protocol. The table below lists the API load names for the currently available modules. + +| Module | API Load Name | Introduced in API Version | +| -------------------------- | ---------------------------------------------------- | ------------------------- | +| Temperature Module GEN1 | `temperature module` or `tempdeck` | 2\.0 | +| Temperature Module GEN2 | `temperature module gen2` | 2\.3 | +| Magnetic Module GEN1 | `magnetic module` or `magdeck` | 2\.0 | +| Magnetic Module GEN2 | `magnetic module gen2` | 2\.3 | +| Thermocycler Module GEN1 | `thermocycler module` or `thermocycler` | 2\.0 | +| Thermocycler Module GEN2 | `thermocycler module gen2` or `thermocyclerModuleV2` | 2\.13 | +| Heater\-Shaker Module GEN1 | `heaterShakerModuleV1` | 2\.13 | +| Magnetic Block GEN1 | `magneticBlockV1` | 2\.15 | + +Some modules were added to our Python API later than others, and others span multiple hardware generations. When writing a protocol that requires a module, make sure your `requirements` or `metadata` code block specifies an [API version](index.html#v2-versioning) high enough to support all the module generations you want to use. + +#### Loading Labware onto a Module + +Use the `load_labware()` method on the module context to load labware on a module. For example, to load the [Opentrons 24 Well Aluminum Block](https://labware.opentrons.com/opentrons_24_aluminumblock_generic_2ml_screwcap?category=aluminumBlock) on top of a Temperature Module: + +``` +def run(protocol: protocol_api.ProtocolContext): + temp_mod = protocol.load_module( + module_name="temperature module gen2", + location="D1") + temp_labware = temp_mod.load_labware( + name="opentrons_24_aluminumblock_generic_2ml_screwcap", + label="Temperature-Controlled Tubes") + +``` + +New in version 2\.0\. + +When you load labware on a module, you don’t need to specify the deck slot. In the above example, the `load_module()` method already specifies where the module is on the deck: `location= "D1"`. + +Any [custom labware](index.html#v2-custom-labware) added to your Opentrons App is also accessible when loading labware onto a module. You can find and copy its load name by going to its card on the Labware page. + +New in version 2\.1\. + +##### Module and Labware Compatibility + +It’s your responsibility to ensure the labware and module combinations you load together work together. The Protocol API won’t raise a warning or error if you load an unusual combination, like placing a tube rack on a Thermocycler. See [What labware can I use with my modules?](https://support.opentrons.com/s/article/What-labware-can-I-use-with-my-modules) for more information about labware/module combinations. + +##### Additional Labware Parameters + +In addition to the mandatory `load_name` argument, you can also specify additional parameters. For example, if you specify a `label`, this name will appear in the Opentrons App and the run log instead of the load name. For labware that has multiple definitions, you can specify `version` and `namespace` (though most of the time you won’t have to). The [`load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') methods of all module contexts accept these additional parameters. + +### Heater\-Shaker Module + +The Heater\-Shaker Module provides on\-deck heating and orbital shaking. The module can heat from 37 to 95 °C, and can shake samples from 200 to 3000 rpm. + +The Heater\-Shaker Module is represented in code by a [`HeaterShakerContext`](index.html#opentrons.protocol_api.HeaterShakerContext 'opentrons.protocol_api.HeaterShakerContext') object. For example: + +``` +hs_mod = protocol.load_module( + module_name="heaterShakerModuleV1", location="D1" +) + +``` + +New in version 2\.13\. + +#### Deck Slots + +The supported deck slot positions for the Heater\-Shaker depend on the robot you’re using. + +| Robot Model | Heater\-Shaker Deck Placement | +| ----------- | ----------------------------------------------------------------------------------------------------------- | +| Flex | In any deck slot in column 1 or 3\. The module can go in slot A3, but you need to move the trash bin first. | +| OT\-2 | In deck slot 1, 3, 4, 6, 7, or 10\. | + +#### OT\-2 Placement Restrictions + +On OT\-2, you need to restrict placement of other modules and labware around the Heater\-Shaker. On Flex, the module is installed below\-deck in a caddy and there is more space between deck slots, so these restrictions don’t apply. + +In general, it’s best to leave all slots adjacent to the Heater\-Shaker empty. If your protocol requires filling those slots, observe the following restrictions to avoid physical crashes involving the Heater\-Shaker. + +##### Adjacent Modules + +Do not place other modules next to the Heater\-Shaker. Keeping adjacent deck slots clear helps prevents collisions during shaking and while opening the labware latch. Loading a module next to the Heater\-Shaker on OT\-2 will raise a `DeckConflictError`. + +##### Tall Labware + +Do not place labware taller than 53 mm to the left or right of the Heater\-Shaker. This prevents the Heater\-Shaker’s latch from colliding with the adjacent labware. Common labware that exceed the height limit include Opentrons tube racks and Opentrons 1000 µL tip racks. Loading tall labware to the right or left of the Heater\-Shaker on OT\-2 will raise a `DeckConflictError`. + +##### 8\-Channel Pipettes + +You can’t perform pipetting actions in any slots adjacent to the Heater\-Shaker if you’re using a GEN2 or GEN1 8\-channel pipette. This prevents the pipette ejector from crashing on the module housing or labware latch. Using an 8\-channel pipette will raise a `PipetteMovementRestrictedByHeaterShakerError`. + +There is one exception: to the front or back of the Heater\-Shaker, an 8\-channel pipette can access tip racks only. Attempting to pipette to non\-tip\-rack labware will also raise a `PipetteMovementRestrictedByHeaterShakerError`. + +#### Latch Control + +To add and remove labware from the Heater\-Shaker, control the module’s labware latch from your protocol using [`open_labware_latch()`](index.html#opentrons.protocol_api.HeaterShakerContext.open_labware_latch 'opentrons.protocol_api.HeaterShakerContext.open_labware_latch') and [`close_labware_latch()`](index.html#opentrons.protocol_api.HeaterShakerContext.close_labware_latch 'opentrons.protocol_api.HeaterShakerContext.close_labware_latch'). Shaking requires the labware latch to be closed, so you may want to issue a close command before the first shake command in your protocol: + +``` +hs_mod.close_labware_latch() +hs_mod.set_and_wait_for_shake_speed(500) + +``` + +If the labware latch is already closed, `close_labware_latch()` will succeed immediately; you don’t have to check the status of the latch before opening or closing it. + +To prepare the deck before running a protocol, use the labware latch controls in the Opentrons App or run these methods in Jupyter notebook. + +#### Loading Labware + +Use the Heater\-Shaker’s [`load_adapter()`](index.html#opentrons.protocol_api.HeaterShakerContext.load_adapter 'opentrons.protocol_api.HeaterShakerContext.load_adapter') and [`load_labware()`](index.html#opentrons.protocol_api.HeaterShakerContext.load_labware 'opentrons.protocol_api.HeaterShakerContext.load_labware') methods to specify what you will place on the module. For the Heater\-Shaker, use one of the thermal adapters listed below and labware that fits on the adapter. See [Loading Labware on Adapters](index.html#labware-on-adapters) for examples of loading labware on modules. + +The [Opentrons Labware Library](https://labware.opentrons.com/) includes definitions for both standalone adapters and adapter–labware combinations. These labware definitions help make the Heater\-Shaker ready to use right out of the box. + +Note + +If you plan to [move labware](index.html#moving-labware) onto or off of the Heater\-Shaker during your protocol, you must use a standalone adapter definition, not an adapter–labware combination definiton. + +##### Standalone Adapters + +You can use these standalone adapter definitions to load Opentrons verified or custom labware on top of the Heater\-Shaker. + +| Adapter Type | API Load Name | +| ----------------------------------------------- | ---------------------------------- | +| Opentrons Universal Flat Heater\-Shaker Adapter | `opentrons_universal_flat_adapter` | +| Opentrons 96 PCR Heater\-Shaker Adapter | `opentrons_96_pcr_adapter` | +| Opentrons 96 Deep Well Heater\-Shaker Adapter | `opentrons_96_deep_well_adapter` | +| Opentrons 96 Flat Bottom Heater\-Shaker Adapter | `opentrons_96_flat_bottom_adapter` | + +For example, these commands load a well plate on top of the flat bottom adapter: + +``` +hs_adapter = hs_mod.load_adapter("opentrons_96_flat_bottom_adapter") +hs_plate = hs_adapter.load_labware("nest_96_wellplate_200ul_flat") + +``` + +New in version 2\.15: The `load_adapter()` method. + +##### Pre\-configured Combinations + +The Heater\-Shaker supports these thermal adapter and labware combinations for backwards compatibility. If your protocol specifies an `apiLevel` of 2\.15 or higher, you should use the standalone adapter definitions instead. + +| Adapter/Labware Combination | API Load Name | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------- | +| Opentrons 96 Deep Well Adapter with NEST Deep Well Plate 2 mL | `opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep` | +| Opentrons 96 Flat Bottom Adapter with NEST 96 Well Plate 200 µL Flat | `opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat` | +| Opentrons 96 PCR Adapter with Armadillo Well Plate 200 µL | `opentrons_96_pcr_adapter_armadillo_wellplate_200ul` | +| Opentrons 96 PCR Adapter with NEST Well Plate 100 µL | `opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt` | +| Opentrons Universal Flat Adapter with Corning 384 Well Plate 112 µL Flat | `opentrons_universal_flat_adapter_corning_384_wellplate_112ul_flat` | + +This command loads the same physical adapter and labware as the example in the previous section, but it is also compatible with API versions 2\.13 and 2\.14: + +``` +hs_combo = hs_mod.load_labware( + "opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat" +) + +``` + +New in version 2\.13\. + +##### Custom Flat\-Bottom Labware + +Custom flat\-bottom labware can be used with the Universal Flat Adapter. See the support article [Requesting a Custom Labware Definition](https://support.opentrons.com/s/article/Requesting-a-custom-labware-definition) if you need assistance creating custom labware definitions for the Heater\-Shaker. + +#### Heating and Shaking + +The API treats heating and shaking as separate, independent activities due to the amount of time they take. + +Increasing or reducing shaking speed takes a few seconds, so the API treats these actions as _blocking_ commands. All other commands cannot run until the module reaches the required speed. + +Heating the module, or letting it passively cool, takes more time than changing the shaking speed. As a result, the API gives you the flexibility to perform other pipetting actions while waiting for the module to reach a target temperature. When holding at temperature, you can design your protocol to run in a blocking or non\-blocking manner. + +Note + +Since API version 2\.13, only the Heater\-Shaker Module supports non\-blocking command execution. All other modules’ methods are blocking commands. + +##### Blocking commands + +This example uses a blocking command and shakes a sample for one minute. No other commands will execute until a minute has elapsed. The three commands in this example start the shake, wait for one minute, and then stop the shake: + +``` +hs_mod.set_and_wait_for_shake_speed(500) +protocol.delay(minutes=1) +hs_mod.deactivate_shaker() + +``` + +These actions will take about 65 seconds total. Compare this with similar\-looking commands for holding a sample at a temperature for one minute: + +``` +hs_mod.set_and_wait_for_temperature(75) +protocol.delay(minutes=1) +hs_mod.deactivate_heater() + +``` + +This may take much longer, depending on the thermal block used, the volume and type of liquid contained in the labware, and the initial temperature of the module. + +##### Non\-blocking commands + +To pipette while the Heater\-Shaker is heating, use [`set_target_temperature()`](index.html#opentrons.protocol_api.HeaterShakerContext.set_target_temperature 'opentrons.protocol_api.HeaterShakerContext.set_target_temperature') and [`wait_for_temperature()`](index.html#opentrons.protocol_api.HeaterShakerContext.wait_for_temperature 'opentrons.protocol_api.HeaterShakerContext.wait_for_temperature') instead of [`set_and_wait_for_temperature()`](index.html#opentrons.protocol_api.HeaterShakerContext.set_and_wait_for_temperature 'opentrons.protocol_api.HeaterShakerContext.set_and_wait_for_temperature'): + +``` +hs_mod.set_target_temperature(75) +pipette.pick_up_tip() +pipette.aspirate(50, plate["A1"]) +pipette.dispense(50, plate["B1"]) +pipette.drop_tip() +hs_mod.wait_for_temperature() +protocol.delay(minutes=1) +hs_mod.deactivate_heater() + +``` + +This example would likely take just as long as the blocking version above; it’s unlikely that one aspirate and one dispense action would take longer than the time for the module to heat. However, be careful when putting a lot of commands between a `set_target_temperature()` call and a `delay()` call. In this situation, you’re relying on `wait_for_temperature()` to resume execution of commands once heating is complete. But if the temperature has already been reached, the delay will begin later than expected and the Heater\-Shaker will hold at its target temperature longer than intended. + +Additionally, if you want to pipette while the module holds a temperature for a certain length of time, you need to track the holding time yourself. One of the simplest ways to do this is with Python’s `time` module. First, add `import time` at the start of your protocol. Then, use [`time.monotonic()`](https://docs.python.org/3/library/time.html#time.monotonic '(in Python v3.12)') to set a reference time when the target is reached. Finally, add a delay that calculates how much holding time is remaining after the pipetting actions: + +``` +hs_mod.set_and_wait_for_temperature(75) +start_time = time.monotonic() # set reference time +pipette.pick_up_tip() +pipette.aspirate(50, plate["A1"]) +pipette.dispense(50, plate["B1"]) +pipette.drop_tip() +# delay for the difference between now and 60 seconds after the reference time +protocol.delay(max(0, start_time+60 - time.monotonic())) +hs_mod.deactivate_heater() + +``` + +Provided that the parallel pipetting actions don’t take more than one minute, this code will deactivate the heater one minute after its target was reached. If more than one minute has elapsed, the value passed to `protocol.delay()` will equal 0, and the protocol will continue immediately. + +#### Deactivating + +Deactivating the heater and shaker are done separately using the [`deactivate_heater()`](index.html#opentrons.protocol_api.HeaterShakerContext.deactivate_heater 'opentrons.protocol_api.HeaterShakerContext.deactivate_heater') and [`deactivate_shaker()`](index.html#opentrons.protocol_api.HeaterShakerContext.deactivate_shaker 'opentrons.protocol_api.HeaterShakerContext.deactivate_shaker') methods, respectively. There is no method to deactivate both simultaneously. Call the two methods in sequence if you need to stop both heating and shaking. + +Note + +The robot will not automatically deactivate the Heater\-Shaker at the end of a protocol. If you need to deactivate the module after a protocol is completed or canceled, use the Heater\-Shaker module controls on the device detail page in the Opentrons App or run these methods in Jupyter notebook. + +### Magnetic Block + +Note + +The Magnetic Block is compatible with Opentrons Flex only. If you have an OT\-2, use the [Magnetic Module](index.html#magnetic-module). + +The Magnetic Block is an unpowered, 96\-well plate that holds labware close to its high\-strength neodymium magnets. This module is suitable for many magnetic bead\-based protocols, but does not move beads up or down in solution. + +Because the Magnetic Block is unpowered, neither your robot nor the Opentrons App aware of this module. You “control” it via protocols to load labware onto the module and use the Opentrons Flex Gripper to move labware on and off the module. See [Moving Labware](index.html#moving-labware) for more information. + +The Magnetic Block is represented by a [`MagneticBlockContext`](index.html#opentrons.protocol_api.MagneticBlockContext 'opentrons.protocol_api.MagneticBlockContext') object which lets you load labware on top of the module. + +``` +# Load the Magnetic Block in deck slot D1 +magnetic_block = protocol.load_module( + module_name="magneticBlockV1", location="D1" +) + +# Load a 96-well plate on the magnetic block +mag_plate = magnetic_block.load_labware( + name="biorad_96_wellplate_200ul_pcr" +) + +# Use the Gripper to move labware +protocol.move_labware(mag_plate, new_location="B2", use_gripper=True) + +``` + +New in version 2\.15\. + +### Magnetic Module + +Note + +The Magnetic Module is compatible with the OT\-2 only. If you have a Flex, use the [Magnetic Block](index.html#magnetic-block). + +The Magnetic Module controls a set of permanent magnets which can move vertically to induce a magnetic field in the labware loaded on the module. + +The Magnetic Module is represented by a [`MagneticModuleContext`](index.html#opentrons.protocol_api.MagneticModuleContext 'opentrons.protocol_api.MagneticModuleContext') object, which has methods for engaging (raising) and disengaging (lowering) its magnets. + +The examples in this section apply to an OT\-2 with a Magnetic Module GEN2 loaded in slot 6: + +``` +def run(protocol: protocol_api.ProtocolContext): + mag_mod = protocol.load_module( + module_name="magnetic module gen2", + location="6") + plate = mag_mod.load_labware( + name="nest_96_wellplate_100ul_pcr_full_skirt") + +``` + +New in version 2\.3\. + +#### Loading Labware + +Like with all modules, use the Magnetic Module’s [`load_labware()`](index.html#opentrons.protocol_api.MagneticModuleContext.load_labware 'opentrons.protocol_api.MagneticModuleContext.load_labware') method to specify what you will place on the module. The Magnetic Module supports 96\-well PCR plates and deep well plates. For the best compatibility, use a labware definition that specifies how far the magnets should move when engaging with the labware. The following plates in the [Opentrons Labware Library](https://labware.opentrons.com/) include this measurement: + +| Labware Name | API Load Name | +| -------------------------------------------- | ------------------------------------------ | +| Bio\-Rad 96 Well Plate 200 µL PCR | `biorad_96_wellplate_200ul_pcr` | +| NEST 96 Well Plate 100 µL PCR Full Skirt | `nest_96_wellplate_100ul_pcr_full_skirt` | +| NEST 96 Deep Well Plate 2mL | `nest_96_wellplate_2ml_deep` | +| Thermo Scientific Nunc 96 Well Plate 1300 µL | `thermoscientificnunc_96_wellplate_1300ul` | +| Thermo Scientific Nunc 96 Well Plate 2000 µL | `thermoscientificnunc_96_wellplate_2000ul` | +| USA Scientific 96 Deep Well Plate 2\.4 mL | `usascientific_96_wellplate_2.4ml_deep` | + +To check whether a custom labware definition specifies this measurement, load the labware and query its [`magdeck_engage_height`](index.html#opentrons.protocol_api.Labware.magdeck_engage_height 'opentrons.protocol_api.Labware.magdeck_engage_height') property. If has a numerical value, the labware is ready for use with the Magnetic Module. + +#### Engaging and Disengaging + +Raise and lower the module’s magnets with the [`engage()`](index.html#opentrons.protocol_api.MagneticModuleContext.engage 'opentrons.protocol_api.MagneticModuleContext.engage') and [`disengage()`](index.html#opentrons.protocol_api.MagneticModuleContext.disengage 'opentrons.protocol_api.MagneticModuleContext.disengage') functions, respectively. + +If your loaded labware is fully compatible with the Magnetic Module, you can call `engage()` with no argument: + +> ``` +> mag_mod.engage() +> +> ``` +> +> New in version 2\.0\. + +This will move the magnets upward to the default height for the labware, which should be close to the bottom of the labware’s wells. If your loaded labware doesn’t specify a default height, this will raise an `ExceptionInProtocolError`. + +For certain applications, you may want to move the magnets to a different height. The recommended way is to use the `height_from_base` parameter, which represents the distance above the base of the labware (its lowest point, where it rests on the module). Setting `height_from_base=0` should move the tops of the magnets level with the base of the labware. Alternatively, you can use the `offset` parameter, which represents the distance above _or below_ the labware’s default position (close to the bottom of its wells). Like using `engage()` with no argument, this will raise an error if there is no default height for the loaded labware. + +Note + +There is up to 1 mm of manufacturing variance across Magnetic Module units, so observe the exact position and adjust as necessary before running your protocol. + +Here are some examples of where the magnets will move when using the different parameters in combination with the loaded NEST PCR plate, which specifies a default height of 20 mm: + +> ``` +> mag_mod.engage(height_from_base=13.5) # 13.5 mm +> mag_mod.engage(offset=-2) # 15.5 mm +> +> ``` + +Note that `offset` takes into account the fact that the magnets’ home position is measured as −2\.5 mm for GEN2 modules. + +> New in version 2\.0\. +> +> Changed in version 2\.2: Added the `height_from_base` parameter. + +When you need to retract the magnets back to their home position, call [`disengage()`](index.html#opentrons.protocol_api.MagneticModuleContext.disengage 'opentrons.protocol_api.MagneticModuleContext.disengage'). + +> ``` +> mag_mod.disengage() # -2.5 mm +> +> ``` + +New in version 2\.0\. + +If at any point you need to check whether the magnets are engaged or not, use the [`status`](index.html#opentrons.protocol_api.MagneticModuleContext.status 'opentrons.protocol_api.MagneticModuleContext.status') property. This will return either the string `engaged` or `disengaged`, not the exact height of the magnets. + +Note + +The OT\-2 will not automatically deactivate the Magnetic Module at the end of a protocol. If you need to deactivate the module after a protocol is completed or canceled, use the Magnetic Module controls on the device detail page in the Opentrons App or run `deactivate()` in Jupyter notebook. + +#### Changes with the GEN2 Magnetic Module + +The GEN2 Magnetic Module uses smaller magnets than the GEN1 version. This change helps mitigate an issue with the magnets attracting beads from their retracted position, but it also takes longer for the GEN2 module to attract beads. The recommended attraction time is 5 minutes for liquid volumes up to 50 µL and 7 minutes for volumes greater than 50 µL. If your application needs additional magnetic strength to attract beads within these timeframes, use the available [Adapter Magnets](https://support.opentrons.com/s/article/Adapter-magnets). + +### Temperature Module + +The Temperature Module acts as both a cooling and heating device. It can control the temperature of its deck between 4 °C and 95 °C with a resolution of 1 °C. + +The Temperature Module is represented in code by a [`TemperatureModuleContext`](index.html#opentrons.protocol_api.TemperatureModuleContext 'opentrons.protocol_api.TemperatureModuleContext') object, which has methods for setting target temperatures and reading the module’s status. This example demonstrates loading a Temperature Module GEN2 and loading a well plate on top of it. + +``` +temp_mod = protocol.load_module( + module_name="temperature module gen2", location="D3" +) + +``` + +New in version 2\.3\. + +#### Loading Labware + +Use the Temperature Module’s [`load_adapter()`](index.html#opentrons.protocol_api.TemperatureModuleContext.load_adapter 'opentrons.protocol_api.TemperatureModuleContext.load_adapter') and [`load_labware()`](index.html#opentrons.protocol_api.TemperatureModuleContext.load_labware 'opentrons.protocol_api.TemperatureModuleContext.load_labware') methods to specify what you will place on the module. You may use one or both of the methods, depending on the labware you’re using. See [Loading Labware on Adapters](index.html#labware-on-adapters) for examples of loading labware on modules. + +The [Opentrons Labware Library](https://labware.opentrons.com/) includes definitions for both standalone adapters and adapter–labware combinations. These labware definitions help make the Temperature Module ready to use right out of the box. + +##### Standalone Adapters + +You can use these standalone adapter definitions to load Opentrons verified or custom labware on top of the Temperature Module. + +| Adapter Type | API Load Name | +| ------------------------------------ | -------------------------------------- | +| Opentrons Aluminum Flat Bottom Plate | `opentrons_aluminum_flat_bottom_plate` | +| Opentrons 96 Well Aluminum Block | `opentrons_96_well_aluminum_block` | + +For example, these commands load a PCR plate on top of the 96\-well block: + +``` +temp_adapter = temp_mod.load_adapter( + "opentrons_96_well_aluminum_block" +) +temp_plate = temp_adapter.load_labware( + "nest_96_wellplate_100ul_pcr_full_skirt" +) + +``` + +New in version 2\.15: The `load_adapter()` method. + +Note + +You can also load labware directly onto the Temperature Module. In API version 2\.14 and earlier, this was the correct way to load labware on top of the flat bottom plate. In API version 2\.15 and later, you should load both the adapter and the labware with separate commands. + +##### Block\-and\-tube combinations + +You can use these combination labware definitions to load various types of tubes into the 24\-well thermal block on top of the Temperature Module. There is no standalone definition for the 24\-well block. + +| Tube Type | API Load Name | +| ---------------------- | ------------------------------------------------- | +| Generic 2 mL screw cap | `opentrons_24_aluminumblock_generic_2ml_screwcap` | +| NEST 0\.5 mL screw cap | `opentrons_24_aluminumblock_nest_0.5ml_screwcap` | +| NEST 1\.5 mL screw cap | `opentrons_24_aluminumblock_nest_1.5ml_screwcap` | +| NEST 1\.5 mL snap cap | `opentrons_24_aluminumblock_nest_1.5ml_snapcap` | +| NEST 2 mL screw cap | `opentrons_24_aluminumblock_nest_2ml_screwcap` | +| NEST 2 mL snap cap | `opentrons_24_aluminumblock_nest_2ml_snapcap` | + +For example, this command loads the 24\-well block with generic 2 mL tubes: + +``` +temp_tubes = temp_mod.load_labware( + "opentrons_24_aluminumblock_generic_2ml_screwcap" +) + +``` + +New in version 2\.0\. + +##### Block\-and\-plate combinations + +The Temperature Module supports these 96\-well block and labware combinations for backwards compatibility. If your protocol specifies an `apiLevel` of 2\.15 or higher, you should use the standalone 96\-well block definition instead. + +| 96\-well block contents | API Load Name | +| -------------------------- | ---------------------------------------------------- | +| Bio\-Rad well plate 200 μL | `opentrons_96_aluminumblock_biorad_wellplate_200uL` | +| Generic PCR strip 200 μL | `opentrons_96_aluminumblock_generic_pcr_strip_200uL` | +| NEST well plate 100 μL | `opentrons_96_aluminumblock_nest_wellplate_100uL` | + +This command loads the same physical adapter and labware as the example in the Standalone Adapters section above, but it is also compatible with earlier API versions: + +``` +temp_combo = temp_mod.load_labware( + "opentrons_96_aluminumblock_nest_wellplate_100uL" +) + +``` + +New in version 2\.0\. + +#### Temperature Control + +The primary function of the module is to control the temperature of its deck, using [`set_temperature()`](index.html#opentrons.protocol_api.TemperatureModuleContext.set_temperature 'opentrons.protocol_api.TemperatureModuleContext.set_temperature'), which takes one parameter: `celsius`. For example, to set the Temperature Module to 4 °C: + +``` +temp_mod.set_temperature(celsius=4) + +``` + +When using `set_temperature()`, your protocol will wait until the target temperature is reached before proceeding to further commands. In other words, you can pipette to or from the Temperature Module when it is holding at a temperature or idle, but not while it is actively changing temperature. Whenever the module reaches its target temperature, it will hold the temperature until you set a different target or call [`deactivate()`](index.html#opentrons.protocol_api.TemperatureModuleContext.deactivate 'opentrons.protocol_api.TemperatureModuleContext.deactivate'), which will stop heating or cooling and will turn off the fan. + +Note + +Your robot will not automatically deactivate the Temperature Module at the end of a protocol. If you need to deactivate the module after a protocol is completed or canceled, use the Temperature Module controls on the device detail page in the Opentrons App or run `deactivate()` in Jupyter notebook. + +New in version 2\.0\. + +#### Temperature Status + +If you need to confirm in software whether the Temperature Module is holding at a temperature or is idle, use the [`status`](index.html#opentrons.protocol_api.TemperatureModuleContext.status 'opentrons.protocol_api.TemperatureModuleContext.status') property: + +``` +temp_mod.set_temperature(celsius=90) +temp_mod.status # "holding at target" +temp_mod.deactivate() +temp_mod.status # "idle" + +``` + +If you don’t need to use the status value in your code, and you have physical access to the module, you can read its status and temperature from the LED and display on the module. + +New in version 2\.0\. + +#### Changes with the GEN2 Temperature Module + +All methods of [`TemperatureModuleContext`](index.html#opentrons.protocol_api.TemperatureModuleContext 'opentrons.protocol_api.TemperatureModuleContext') work with both the GEN1 and GEN2 Temperature Module. Physically, the GEN2 module has a plastic insulating rim around the plate, and plastic insulating shrouds designed to fit over Opentrons aluminum blocks. This mitigates an issue where the GEN1 module would have trouble cooling to very low temperatures, especially if it shared the deck with a running Thermocycler. + +### Thermocycler Module + +The Thermocycler Module provides on\-deck, fully automated thermocycling, and can heat and cool very quickly during operation. The module’s block can reach and maintain temperatures between 4 and 99 °C. The module’s lid can heat up to 110 °C. + +The Thermocycler is represented in code by a [`ThermocyclerContext`](index.html#opentrons.protocol_api.ThermocyclerContext 'opentrons.protocol_api.ThermocyclerContext') object, which has methods for controlling the lid, controlling the block, and setting _profiles_ — timed heating and cooling routines that can be repeated automatically. + +The examples in this section will use a Thermocycler Module GEN2 loaded as follows: + +``` +tc_mod = protocol.load_module(module_name="thermocyclerModuleV2") +plate = tc_mod.load_labware(name="nest_96_wellplate_100ul_pcr_full_skirt") + +``` + +New in version 2\.13\. + +#### Lid Control + +The Thermocycler can control the position and temperature of its lid. + +To change the lid position, use [`open_lid()`](index.html#opentrons.protocol_api.ThermocyclerContext.open_lid 'opentrons.protocol_api.ThermocyclerContext.open_lid') and [`close_lid()`](index.html#opentrons.protocol_api.ThermocyclerContext.close_lid 'opentrons.protocol_api.ThermocyclerContext.close_lid'). When the lid is open, the pipettes can access the loaded labware. + +You can also control the temperature of the lid. Acceptable target temperatures are between 37 and 110 °C. Use [`set_lid_temperature()`](index.html#opentrons.protocol_api.ThermocyclerContext.set_lid_temperature 'opentrons.protocol_api.ThermocyclerContext.set_lid_temperature'), which takes one parameter: the target `temperature` (in degrees Celsius) as an integer. For example, to set the lid to 50 °C: + +``` +tc_mod.set_lid_temperature(temperature=50) + +``` + +The protocol will only proceed once the lid temperature reaches 50 °C. This is the case whether the previous temperature was lower than 50 °C (in which case the lid will actively heat) or higher than 50 °C (in which case the lid will passively cool). + +You can turn off the lid heater at any time with [`deactivate_lid()`](index.html#opentrons.protocol_api.ThermocyclerContext.deactivate_lid 'opentrons.protocol_api.ThermocyclerContext.deactivate_lid'). + +Note + +Lid temperature is not affected by Thermocycler profiles. Therefore you should set an appropriate lid temperature to hold during your profile _before_ executing it. See [Thermocycler Profiles](#thermocycler-profiles) for more information on defining and executing profiles. + +New in version 2\.0\. + +#### Block Control + +The Thermocycler can control its block temperature, including holding at a temperature and adjusting for the volume of liquid held in its loaded plate. + +##### Temperature + +To set the block temperature inside the Thermocycler, use [`set_block_temperature()`](index.html#opentrons.protocol_api.ThermocyclerContext.set_block_temperature 'opentrons.protocol_api.ThermocyclerContext.set_block_temperature'). At minimum you have to specify a `temperature` in degrees Celsius: + +``` +tc_mod.set_block_temperature(temperature=4) + +``` + +If you don’t specify any other parameters, the Thermocycler will hold this temperature until a new temperature is set, [`deactivate_block()`](index.html#opentrons.protocol_api.ThermocyclerContext.deactivate_block 'opentrons.protocol_api.ThermocyclerContext.deactivate_block') is called, or the module is powered off. + +New in version 2\.0\. + +##### Hold Time + +You can optionally instruct the Thermocycler to hold its block temperature for a specific amount of time. You can specify `hold_time_minutes`, `hold_time_seconds`, or both (in which case they will be added together). For example, this will set the block to 4 °C for 4 minutes and 15 seconds: + +``` +tc_mod.set_block_temperature( + temperature=4, + hold_time_minutes=4, + hold_time_seconds=15) + +``` + +Note + +Your protocol will not proceed to further commands while holding at a temperature. If you don’t specify a hold time, the protocol will proceed as soon as the target temperature is reached. + +New in version 2\.0\. + +##### Block Max Volume + +The Thermocycler’s block temperature controller varies its behavior based on the amount of liquid in the wells of its labware. Accurately specifying the liquid volume allows the Thermocycler to more precisely control the temperature of the samples. You should set the `block_max_volume` parameter to the amount of liquid in the _fullest_ well, measured in µL. If not specified, the Thermocycler will assume samples of 25 µL. + +It is especially important to specify `block_max_volume` when holding at a temperature. For example, say you want to hold larger samples at a temperature for a short time: + +``` +tc_mod.set_block_temperature( + temperature=4, + hold_time_seconds=20, + block_max_volume=80) + +``` + +If the Thermocycler assumes these samples are 25 µL, it may not cool them to 4 °C before starting the 20\-second timer. In fact, with such a short hold time they may not reach 4 °C at all! + +New in version 2\.0\. + +#### Thermocycler Profiles + +In addition to executing individual temperature commands, the Thermocycler can automatically cycle through a sequence of block temperatures to perform heat\-sensitive reactions. These sequences are called _profiles_, which are defined in the Protocol API as lists of dictionaries. Each dictionary within the profile should have a `temperature` key, which specifies the temperature of the step, and either or both of `hold_time_seconds` and `hold_time_minutes`, which specify the duration of the step. + +For example, this profile commands the Thermocycler to reach 10 °C and hold for 30 seconds, and then to reach 60 °C and hold for 45 seconds: + +``` +profile = [ + {"temperature":10, "hold_time_seconds":30}, + {"temperature":60, "hold_time_seconds":45} +] + +``` + +Once you have written the steps of your profile, execute it with [`execute_profile()`](index.html#opentrons.protocol_api.ThermocyclerContext.execute_profile 'opentrons.protocol_api.ThermocyclerContext.execute_profile'). This function executes your profile steps multiple times depending on the `repetitions` parameter. It also takes a `block_max_volume` parameter, which is the same as that of the [`set_block_temperature()`](index.html#opentrons.protocol_api.ThermocyclerContext.set_block_temperature 'opentrons.protocol_api.ThermocyclerContext.set_block_temperature') function. + +For instance, a PCR prep protocol might define and execute a profile like this: + +``` +profile = [ + {"temperature":95, "hold_time_seconds":30}, + {"temperature":57, "hold_time_seconds":30}, + {"temperature":72, "hold_time_seconds":60} +] +tc_mod.execute_profile(steps=profile, repetitions=20, block_max_volume=32) + +``` + +In terms of the actions that the Thermocycler performs, this would be equivalent to nesting `set_block_temperature` commands in a `for` loop: + +``` +for i in range(20): + tc_mod.set_block_temperature(95, hold_time_seconds=30, block_max_volume=32) + tc_mod.set_block_temperature(57, hold_time_seconds=30, block_max_volume=32) + tc_mod.set_block_temperature(72, hold_time_seconds=60, block_max_volume=32) + +``` + +However, this code would generate 60 lines in the protocol’s run log, while executing a profile is summarized in a single line. Additionally, you can set a profile once and execute it multiple times (with different numbers of repetitions and maximum volumes, if needed). + +Note + +Temperature profiles only control the temperature of the block in the Thermocycler. You should set a lid temperature before executing the profile using [`set_lid_temperature()`](index.html#opentrons.protocol_api.ThermocyclerContext.set_lid_temperature 'opentrons.protocol_api.ThermocyclerContext.set_lid_temperature'). + +New in version 2\.0\. + +#### Changes with the GEN2 Thermocycler Module + +All methods of [`ThermocyclerContext`](index.html#opentrons.protocol_api.ThermocyclerContext 'opentrons.protocol_api.ThermocyclerContext') work with both the GEN1 and GEN2 Thermocycler. One practical difference is that the GEN2 module has a plate lift feature to make it easier to remove the plate manually or with the Opentrons Flex Gripper. To activate the plate lift, press the button on the Thermocycler for three seconds while the lid is open. If you need to do this in the middle of a run, call [`pause()`](index.html#opentrons.protocol_api.ProtocolContext.pause 'opentrons.protocol_api.ProtocolContext.pause'), lift and move the plate, and then resume the run. + +### Multiple Modules of the Same Type + +You can use multiple modules of the same type within a single protocol. The exception is the Thermocycler Module, which has only one supported deck location because of its size. Running protocols with multiple modules of the same type requires version 4\.3 or newer of the Opentrons App and robot server. + +When working with multiple modules of the same type, load them in your protocol according to their USB port number. Deck coordinates are required by the [`load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') method, but location does not determine which module loads first. Your robot will use the module with the lowest USB port number _before_ using a module of the same type that’s connected to higher numbered USB port. The USB port number (not deck location) determines module load sequence, starting with the lowest port number first. + +### Flex + +In this example, `temperature_module_1` loads first because it’s connected to USB port 2\. `temperature_module_2` loads next because it’s connected to USB port 6\. + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # Load Temperature Module 1 in deck slot D1 on USB port 2 + temperature_module_1 = protocol.load_module( + module_name="temperature module gen2", + location="D1") + + # Load Temperature Module 2 in deck slot C1 on USB port 6 + temperature_module_2 = protocol.load_module( + module_name="temperature module gen2", + location="C1") + +``` + +The Temperature Modules are connected as shown here: + +### OT-2 + +In this example, `temperature_module_1` loads first because it’s connected to USB port 1\. `temperature_module_2` loads next because it’s connected to USB port 3\. + +``` +from opentrons import protocol_api + +metadata = {"apiLevel": "2.19"} + + +def run(protocol: protocol_api.ProtocolContext): + # Load Temperature Module 1 in deck slot C1 on USB port 1 + temperature_module_1 = protocol.load_module( + load_name="temperature module gen2", location="1" + ) + + # Load Temperature Module 2 in deck slot D3 on USB port 2 + temperature_module_2 = protocol.load_module( + load_name="temperature module gen2", location="3" + ) + +``` + +The Temperature Modules are connected as shown here: + +Before running your protocol, it’s a good idea to use the module controls in the Opentrons App to check that commands are being sent where you expect. + +See the support article [Using Modules of the Same Type](https://support.opentrons.com/s/article/Using-modules-of-the-same-type-on-the-OT-2) for more information. + +Hardware modules are powered and unpowered deck\-mounted peripherals. The Flex and OT\-2 are aware of deck\-mounted powered modules when they’re attached via a USB connection and used in an uploaded protocol. The robots do not know about unpowered modules until you use one in a protocol and upload it to the Opentrons App. + +Powered modules include the Heater\-Shaker Module, Magnetic Module, Temperature Module, and Thermocycler Module. The 96\-well Magnetic Block is an unpowered module. + +Pages in this section of the documentation cover: + +> - [Setting up modules and their labware](index.html#module-setup). +> - Working with the module contexts for each type of module. +> +> > - [Heater\-Shaker Module](index.html#heater-shaker-module) +> > - [Magnetic Block](index.html#magnetic-block) +> > - [Magnetic Module](index.html#magnetic-module) +> > - [Temperature Module](index.html#temperature-module) +> > - [Thermocycler Module](index.html#thermocycler-module) +> +> - Working with [multiple modules of the same type](index.html#moam) in a single protocol. + +Note + +Throughout these pages, most code examples use coordinate deck slot locations (e.g. `"D1"`, `"D2"`), like those found on Flex. If you have an OT\-2 and are using API version 2\.14 or earlier, replace the coordinate with its numeric OT\-2 equivalent. For example, slot D1 on Flex corresponds to slot 1 on an OT\-2\. See [Deck Slots](index.html#deck-slots) for more information. + +## Deck Slots + +Deck slots are where you place hardware items on the deck surface of your Opentrons robot. In the API, you load the corresponding items into your protocol with methods like [`ProtocolContext.load_labware`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware'), [`ProtocolContext.load_module`](index.html#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module'), or [`ProtocolContext.load_trash_bin`](index.html#opentrons.protocol_api.ProtocolContext.load_trash_bin 'opentrons.protocol_api.ProtocolContext.load_trash_bin'). When you call these methods, you need to specify which slot to load the item in. + +### Physical Deck Labels + +Flex uses a coordinate labeling system for slots A1 (back left) through D4 (front right). Columns 1 through 3 are in the _working area_ and are accessible by pipettes and the gripper. Column 4 is in the _staging area_ and is only accessible by the gripper. For more information on staging area slots, see [Deck Configuration](#deck-configuration) below. + +OT\-2 uses a numeric labeling system for slots 1 (front left) through 11 (back center). The back right slot is occupied by the fixed trash. + +### API Deck Labels + +The API accepts values that correspond to the physical deck slot labels on a Flex or OT\-2 robot. Specify a slot in either format: + +- A coordinate like `"A1"`. This format must be a string. +- A number like `"10"` or `10`. This format can be a string or an integer. + +As of API version 2\.15, the Flex and OT\-2 formats are interchangeable. You can use either format, regardless of which robot your protocol is for. You could even mix and match formats within a protocol, although this is not recommended. + +For example, these two `load_labware()` commands are equivalent: + +``` +protocol.load_labware("nest_96_wellplate_200ul_flat", "A1") + +``` + +New in version 2\.15\. + +``` +protocol.load_labware("nest_96_wellplate_200ul_flat", 10) + +``` + +New in version 2\.0\. + +Both of these commands would require you to load the well plate in the back left slot of the robot. + +The correspondence between deck labels is based on the relative locations of the slots. The full list of slot equivalencies is as follows: + +| Flex | A1 | A2 | A3 | B1 | B2 | B3 | C1 | C2 | C3 | D1 | D2 | D3 | +| ----- | --- | --- | ----- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| OT\-2 | 10 | 11 | Trash | 7 | 8 | 9 | 4 | 5 | 6 | 1 | 2 | 3 | + +Slots A4, B4, C4, and D4 on Flex have no equivalent on OT\-2\. + +### Deck Configuration + +A Flex running robot system version 7\.1\.0 or higher lets you specify its deck configuration on the touchscreen or in the Opentrons App. This tells the robot the positions of unpowered _deck fixtures_: items that replace standard deck slots. The following table lists currently supported deck fixtures and their allowed deck locations. + +| Fixture | Slots | +| ------------------ | ------------- | +| Staging area slots | A3–D3 | +| Trash bin | A1–D1, A3\-D3 | +| Waste chute | D3 | + +Which fixtures you need to configure depend on both load methods and the effects of other methods called in your protocol. The following sections explain how to configure each type of fixture. + +#### Staging Area Slots + +Slots A4 through D4 are the staging area slots. Pipettes can’t reach the staging area, but these slots are always available in the API for loading and moving labware. Using a slot in column 4 as the `location` argument of [`load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') or the `new_location` argument of [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') will require the corresponding staging area slot in the robot’s deck configuration: + +``` +plate_1 = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", location="C3" +) # no staging slots required +plate_2 = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", location="D4" +) # one staging slot required +protocol.move_labware( + labware=plate_1, new_location="C4" +) # two staging slots required + +``` + +New in version 2\.16\. + +Since staging area slots also include a standard deck slot in column 3, they are physically incompatible with powered modules in the same row of column 3\. For example, if you try to load a module in C3 and labware in C4, the API will raise an error: + +``` +temp_mod = protocol.load_module( + module_name="temperature module gen2", + location="C3" +) +staging_plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", location="C4" +) # deck conflict error + +``` + +It is possible to use slot D4 along with the waste chute. See the [Waste Chute](#configure-waste-chute) section below for details. + +#### Trash Bin + +In version 2\.15 of the API, Flex can only have a single trash bin in slot A3\. You do not have to (and cannot) load the trash in version 2\.15 protocols. + +Starting in API version 2\.16, you must load trash bin fixtures in your protocol in order to use them. Use [`load_trash_bin()`](index.html#opentrons.protocol_api.ProtocolContext.load_trash_bin 'opentrons.protocol_api.ProtocolContext.load_trash_bin') to load a movable trash bin. This example loads a single bin in the default location: + +``` +default_trash = protocol.load_trash_bin(location = "A3") + +``` + +New in version 2\.16\. + +Call `load_trash_bin()` multiple times to add more than one bin. See [Adding Trash Containers](index.html#pipette-trash-containers) for more information on using pipettes with multiple trash bins. + +#### Waste Chute + +The waste chute accepts various materials from Flex pipettes or the Flex Gripper and uses gravity to transport them outside of the robot for disposal. Pipettes can dispose of liquid or drop tips into the chute. The gripper can drop tip racks and other labware into the chute. + +To use the waste chute, first use [`load_waste_chute()`](index.html#opentrons.protocol_api.ProtocolContext.load_waste_chute 'opentrons.protocol_api.ProtocolContext.load_waste_chute') to load it in slot D3: + +``` +chute = protocol.load_waste_chute() + +``` + +New in version 2\.16\. + +The `load_waste_chute()` method takes no arguments, since D3 is the only valid location for the chute. However, there are multiple variant configurations of the waste chute, depending on how other methods in your protocol use it. + +The waste chute is installed either on a standard deck plate adapter or on a deck plate adapter with a staging area. If any [`load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') or [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') calls in your protocol reference slot D4, you have to use the deck plate adapter with staging area. + +The waste chute has a removable cover with a narrow opening which helps prevent aerosols and droplets from contaminating the working area. 1\- and 8\-channel pipettes can dispense liquid, blow out, or drop tips through the opening in the cover. Any of the following require you to remove the cover. + +> - [`dispense()`](index.html#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense'), [`blow_out()`](index.html#opentrons.protocol_api.InstrumentContext.blow_out 'opentrons.protocol_api.InstrumentContext.blow_out'), or [`drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip') with a 96\-channel pipette. +> - [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') with the chute as `new_location` and `use_gripper=True`. + +If your protocol _does not_ call any of these methods, your deck configuration should include the cover. + +In total, there are four possible deck configurations for the waste chute.\* Waste chute only + +- Waste chute with cover +- Waste chute with staging area slot +- Waste chute with staging area slot and cover + +### Deck Conflicts + +A deck conflict check occurs when preparing to run a Python protocol on a Flex running robot system version 7\.1\.0 or higher. The Opentrons App and touchscreen will prevent you from starting the protocol run until any conflicts are resolved. You can resolve them one of two ways: + +> - Physically move hardware around the deck, and update the deck configuration. +> - Alter your protocol to work with the current deck configuration, and resend the protocol to your Flex. + +## Pipettes + +### Loading Pipettes + +When writing a protocol, you must inform the Protocol API about the pipettes you will be using on your robot. The [`ProtocolContext.load_instrument()`](index.html#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument') function provides this information and returns an [`InstrumentContext`](index.html#opentrons.protocol_api.InstrumentContext 'opentrons.protocol_api.InstrumentContext') object. + +As noted above, you call the [`load_instrument()`](index.html#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument') method to load a pipette. This method also requires the [pipette’s API load name](#new-pipette-models), its left or right mount position, and (optionally) a list of associated tip racks. Even if you don’t use the pipette anywhere else in your protocol, the Opentrons App and the robot won’t let you start the protocol run until all pipettes loaded by `load_instrument()` are attached properly. + +#### API Load Names + +The pipette’s API load name (`instrument_name`) is the first parameter of the `load_instrument()` method. It tells your robot which attached pipette you’re going to use in a protocol. The tables below list the API load names for the currently available Flex and OT\-2 pipettes. + +### Flex Pipettes + +| Pipette Model | Volume (µL) | API Load Name | | +| ------------------------ | -------------------- | --------------------- | --- | +| Flex 1\-Channel Pipette | 1–50 | `flex_1channel_50` | | +| 5–1000 | `flex_1channel_1000` | | +| Flex 8\-Channel Pipette | 1–50 | `flex_8channel_50` | | +| 5–1000 | `flex_8channel_1000` | | +| Flex 96\-Channel Pipette | 5–1000 | `flex_96channel_1000` | | + +### OT-2 Pipettes + +| Pipette Model | Volume (µL) | API Load Name | +| -------------------------- | ----------------- | ------------------- | +| P20 Single\-Channel GEN2 | 1\-20 | `p20_single_gen2` | +| P20 Multi\-Channel GEN2 | `p20_multi_gen2` | +| P300 Single\-Channel GEN2 | 20\-300 | `p300_single_gen2` | +| P300 Multi\-Channel GEN2 | `p300_multi_gen2` | +| P1000 Single\-Channel GEN2 | 100\-1000 | `p1000_single_gen2` | + +See the [OT\-2 Pipette Generations](index.html#ot2-pipette-generations) section if you’re using GEN1 pipettes on an OT\-2\. The GEN1 family includes the P10, P50, and P300 single\- and multi\-channel pipettes, along with the P1000 single\-channel model. + +#### Loading Flex 1\- and 8\-Channel Pipettes + +This code sample loads a Flex 1\-Channel Pipette in the left mount and a Flex 8\-Channel Pipette in the right mount. Both pipettes are 1000 µL. Each pipette uses its own 1000 µL tip rack. + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel":"2.19"} + +def run(protocol: protocol_api.ProtocolContext): + tiprack1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", location="D1") + tiprack2 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", location="C1") + left = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=[tiprack1]) + right = protocol.load_instrument( + instrument_name="flex_8channel_1000", + mount="right", + tip_racks=[tiprack2]) + +``` + +If you’re writing a protocol that uses the Flex Gripper, you might think that this would be the place in your protocol to declare that. However, the gripper doesn’t require `load_instrument`! Whether your gripper requires a protocol is determined by the presence of [`ProtocolContext.move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') commands. See [Moving Labware](index.html#moving-labware) for more details. + +#### Loading a Flex 96\-Channel Pipette + +This code sample loads the Flex 96\-Channel Pipette. Because of its size, the Flex 96\-Channel Pipette requires the left _and_ right pipette mounts. You cannot use this pipette with 1\- or 8\-Channel Pipette in the same protocol or when these instruments are attached to the robot. Load the 96\-channel pipette as follows: + +``` +def run(protocol: protocol_api.ProtocolContext): + pipette = protocol.load_instrument( + instrument_name="flex_96channel_1000" + ) + +``` + +In protocols specifying API version 2\.15, also include `mount="left"` as a parameter of `load_instrument()`. + +New in version 2\.15\. + +Changed in version 2\.16: The `mount` parameter is optional. + +#### Loading OT\-2 Pipettes + +This code sample loads a P1000 Single\-Channel GEN2 pipette in the left mount and a P300 Single\-Channel GEN2 pipette in the right mount. Each pipette uses its own 1000 µL tip rack. + +``` +from opentrons import protocol_api + +metadata = {"apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + tiprack1 = protocol.load_labware( + load_name="opentrons_96_tiprack_1000ul", location=1) + tiprack2 = protocol.load_labware( + load_name="opentrons_96_tiprack_1000ul", location=2) + left = protocol.load_instrument( + instrument_name="p1000_single_gen2", + mount="left", + tip_racks=[tiprack1]) + right = protocol.load_instrument( + instrument_name="p300_multi_gen2", + mount="right", + tip_racks=[tiprack1]) + +``` + +New in version 2\.0\. + +#### Adding Tip Racks + +The `load_instrument()` method includes the optional argument `tip_racks`. This parameter accepts a list of tip rack labware objects, which lets you to specify as many tip racks as you want. You can also edit a pipette’s tip racks after loading it by setting its [`InstrumentContext.tip_racks`](index.html#opentrons.protocol_api.InstrumentContext.tip_racks 'opentrons.protocol_api.InstrumentContext.tip_racks') property. + +Note + +Some methods, like [`configure_nozzle_layout()`](index.html#opentrons.protocol_api.InstrumentContext.configure_nozzle_layout 'opentrons.protocol_api.InstrumentContext.configure_nozzle_layout'), reset a pipette’s tip racks. See [Partial Tip Pickup](index.html#partial-tip-pickup) for more information. + +The advantage of using `tip_racks` is twofold. First, associating tip racks with your pipette allows for automatic tip tracking throughout your protocol. Second, it removes the need to specify tip locations in the [`InstrumentContext.pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') method. For example, let’s start by loading loading some labware and instruments like this: + +``` +def run(protocol: protocol_api.ProtocolContext): + tiprack_left = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", location="D1") + tiprack_right = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", location="D2") + left_pipette = protocol.load_instrument( + instrument_name="flex_8channel_1000", mount="left") + right_pipette = protocol.load_instrument( + instrument_name="flex_8channel_1000", + mount="right", + tip_racks=[tiprack_right]) + +``` + +Let’s pick up a tip with the left pipette. We need to specify the location as an argument of `pick_up_tip()`, since we loaded the left pipette without a `tip_racks` argument. + +``` +left_pipette.pick_up_tip(tiprack_left["A1"]) +left_pipette.drop_tip() + +``` + +But now you have to specify `tiprack_left` every time you call `pick_up_tip`, which means you’re doing all your own tip tracking: + +``` +left_pipette.pick_up_tip(tiprack_left["A2"]) +left_pipette.drop_tip() +left_pipette.pick_up_tip(tiprack_left["A3"]) +left_pipette.drop_tip() + +``` + +However, because you specified a tip rack location for the right pipette, the robot will automatically pick up from location `A1` of its associated tiprack: + +``` +right_pipette.pick_up_tip() +right_pipette.drop_tip() + +``` + +Additional calls to `pick_up_tip` will automatically progress through the tips in the right rack: + +``` +right_pipette.pick_up_tip() # picks up from A2 +right_pipette.drop_tip() +right_pipette.pick_up_tip() # picks up from A3 +right_pipette.drop_tip() + +``` + +New in version 2\.0\. + +See also [Building Block Commands](index.html#v2-atomic-commands) and [Complex Commands](index.html#v2-complex-commands). + +#### Adding Trash Containers + +The API automatically assigns a [`trash_container`](index.html#opentrons.protocol_api.InstrumentContext.trash_container 'opentrons.protocol_api.InstrumentContext.trash_container') to pipettes, if one is available in your protocol. The `trash_container` is where the pipette will dispose tips when you call [`drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip') with no arguments. You can change the trash container, if you don’t want to use the default. + +One example of when you might want to change the trash container is a Flex protocol that goes through a lot of tips. In a case where the protocol uses two pipettes, you could load two trash bins and assign one to each pipette: + +``` +left_pipette = protocol.load_instrument( + instrument_name="flex_8channel_1000", mount="left" +) +right_pipette = protocol.load_instrument( + instrument_name="flex_8channel_50", mount="right" +) +left_trash = load_trash_bin("A3") +right_trash = load_trash_bin("B3") +left_pipette.trash_container = left_trash +right_pipette.trash_container = right_trash + +``` + +Another example is a Flex protocol that uses a waste chute. Say you want to only dispose labware in the chute, and you want the pipette to drop tips in a trash bin. You can implicitly get the trash bin to be the pipette’s `trash_container` based on load order, or you can ensure it by setting it after all the load commands: + +``` +pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left" +) +chute = protocol.load_waste_chute() # default because loaded first +trash = protocol.load_trash_bin("A3") +pipette.trash_container = trash # overrides default + +``` + +New in version 2\.0\. + +Changed in version 2\.16: Added support for `TrashBin` and `WasteChute` objects. + +### Pipette Characteristics + +Each Opentrons pipette has different capabilities, which you’ll want to take advantage of in your protocols. This page covers some fundamental pipette characteristics. + +[Multi\-Channel Movement](#new-multichannel-pipettes) gives examples of how multi\-channel pipettes move around the deck by using just one of their channels as a reference point. Taking this into account is important for commanding your pipettes to perform actions in the correct locations. + +[Pipette Flow Rates](#new-plunger-flow-rates) discusses how quickly each type of pipette can handle liquids. The defaults are designed to operate quickly, based on the pipette’s hardware and assuming that you’re handling aqueous liquids. You can speed up or slow down a pipette’s flow rate to suit your protocol’s needs. + +Finally, the volume ranges of pipettes affect what you can do with them. The volume ranges for current pipettes are listed on the [Loading Pipettes](index.html#loading-pipettes) page. The [OT\-2 Pipette Generations](#ot2-pipette-generations) section of this page describes how the API behaves when running protocols that specify older OT\-2 pipettes. + +#### Multi\-Channel Movement + +All [building block](index.html#v2-atomic-commands) and [complex commands](index.html#v2-complex-commands) work with single\- and multi\-channel pipettes. + +To keep the protocol API consistent when using single\- and multi\-channel pipettes, commands treat the back left channel of a multi\-channel pipette as its _primary channel_. Location arguments of pipetting commands use the primary channel. The [`InstrumentContext.configure_nozzle_layout()`](index.html#opentrons.protocol_api.InstrumentContext.configure_nozzle_layout 'opentrons.protocol_api.InstrumentContext.configure_nozzle_layout') method can change the pipette’s primary channel, using its `start` parameter. See [Partial Tip Pickup](index.html#partial-tip-pickup) for more information. + +With a pipette’s default settings, you can generally access the wells indicated in the table below. Moving to any other well may cause the pipette to crash. + +| Channels | 96\-well plate | 384\-well plate | +| -------- | ---------------- | ---------------- | +| 1 | Any well, A1–H12 | Any well, A1–P24 | +| 8 | A1–A12 | A1–B24 | +| 96 | A1 only | A1–B2 | + +Also, you should apply any location offset, such as [`Well.top()`](index.html#opentrons.protocol_api.Well.top 'opentrons.protocol_api.Well.top') or [`Well.bottom()`](index.html#opentrons.protocol_api.Well.bottom 'opentrons.protocol_api.Well.bottom'), to the well accessed by the primary channel. Since all of the pipette’s channels move together, each channel will have the same offset relative to the well that it is over. + +Finally, because each multi\-channel pipette has only one motor, they always aspirate and dispense on all channels simultaneously. + +##### 8\-Channel, 96\-Well Plate Example + +To demonstrate these concepts, let’s write a protocol that uses a Flex 8\-Channel Pipette and a 96\-well plate. We’ll then aspirate and dispense a liquid to different locations on the same well plate. To start, let’s load a pipette in the right mount and add our labware. + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel":"2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # Load a tiprack for 1000 µL tips + tiprack1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", location="D1") + # Load a 96-well plate + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", location="C1") + # Load an 8-channel pipette on the right mount + right = protocol.load_instrument( + instrument_name="flex_8channel_1000", + mount="right", + tip_racks=[tiprack1]) + +``` + +After loading our instruments and labware, let’s tell the robot to pick up a pipette tip from location `A1` in `tiprack1`: + +``` +right.pick_up_tip() + +``` + +With the backmost pipette channel above location A1 on the tip rack, all eight channels are above the eight tip rack wells in column 1\. + +After picking up a tip, let’s tell the robot to aspirate 300 µL from the well plate at location `A2`: + +``` +right.aspirate(volume=300, location=plate["A2"]) + +``` + +With the backmost pipette tip above location A2 on the well plate, all eight channels are above the eight wells in column 2\. + +Finally, let’s tell the robot to dispense 300 µL into the well plate at location `A3`: + +``` +right.dispense(volume=300, location=plate["A3"].top()) + +``` + +With the backmost pipette tip above location A3, all eight channels are above the eight wells in column 3\. The pipette will dispense liquid into all the wells simultaneously. + +##### 8\-Channel, 384\-Well Plate Example + +In general, you should specify wells in the first row of a well plate when using multi\-channel pipettes. An exception to this rule is when using 384\-well plates. The greater well density means the nozzles of a multi\-channel pipette can only access every other well in a column. Specifying well A1 accesses every other well starting with the first (rows A, C, E, G, I, K, M, and O). Similarly, specifying well B1 also accesses every other well, but starts with the second (rows B, D, F, H, J, L, N, and P). + +To demonstrate these concepts, let’s write a protocol that uses a Flex 8\-Channel Pipette and a 384\-well plate. We’ll then aspirate and dispense a liquid to different locations on the same well plate. To start, let’s load a pipette in the right mount and add our labware. + +``` +def run(protocol: protocol_api.ProtocolContext): + # Load a tiprack for 200 µL tips + tiprack1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", location="D1") + # Load a well plate + plate = protocol.load_labware( + load_name="corning_384_wellplate_112ul_flat", location="D2") + # Load an 8-channel pipette on the right mount + right = protocol.load_instrument( + instrument_name="flex_8channel_1000", + mount="right", + tip_racks=[tiprack1]) + +``` + +After loading our instruments and labware, let’s tell the robot to pick up a pipette tip from location `A1` in `tiprack1`: + +``` +right.pick_up_tip() + +``` + +With the backmost pipette channel above location A1 on the tip rack, all eight channels are above the eight tip rack wells in column 1\. + +After picking up a tip, let’s tell the robot to aspirate 100 µL from the well plate at location `A1`: + +``` +right.aspirate(volume=100, location=plate["A1"]) + +``` + +The eight pipette channels will only aspirate from every other well in the column: A1, C1, E1, G1, I1, K1, M1, and O1\. + +Finally, let’s tell the robot to dispense 100 µL into the well plate at location `B1`: + +``` +right.dispense(volume=100, location=plate["B1"]) + +``` + +The eight pipette channels will only dispense into every other well in the column: B1, D1, F1, H1, J1, L1, N1, and P1\. + +#### Pipette Flow Rates + +Measured in µL/s, the flow rate determines how much liquid a pipette can aspirate, dispense, and blow out. Opentrons pipettes have their own default flow rates. The API lets you change the flow rate on a loaded [`InstrumentContext`](index.html#opentrons.protocol_api.InstrumentContext 'opentrons.protocol_api.InstrumentContext') by altering the [`InstrumentContext.flow_rate`](index.html#opentrons.protocol_api.InstrumentContext.flow_rate 'opentrons.protocol_api.InstrumentContext.flow_rate') properties listed below. + +- Aspirate: `InstrumentContext.flow_rate.aspirate` +- Dispense: `InstrumentContext.flow_rate.dispense` +- Blow out: `InstrumentContext.flow_rate.blow_out` + +These flow rate properties operate independently. This means you can specify different flow rates for each property within the same protocol. For example, let’s load a simple protocol and set different flow rates for the attached pipette. + +``` +def run(protocol: protocol_api.ProtocolContext): + tiprack1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", location="D1") + pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=[tiprack1]) + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", location="D3") + pipette.pick_up_tip() + +``` + +Let’s tell the robot to aspirate, dispense, and blow out the liquid using default flow rates. Notice how you don’t need to specify a `flow_rate` attribute to use the defaults: + +``` +pipette.aspirate(200, plate["A1"]) # 160 µL/s +pipette.dispense(200, plate["A2"]) # 160 µL/s +pipette.blow_out() # 80 µL/s + +``` + +Now let’s change the flow rates for each action: + +``` +pipette.flow_rate.aspirate = 50 +pipette.flow_rate.dispense = 100 +pipette.flow_rate.blow_out = 75 +pipette.aspirate(200, plate["A1"]) # 50 µL/s +pipette.dispense(200, plate["A2"]) # 100 µL/s +pipette.blow_out() # 75 µL/s + +``` + +These flow rates will remain in effect until you change the `flow_rate` attribute again _or_ call `configure_for_volume()`. Calling `configure_for_volume()` always resets all pipette flow rates to the defaults for the mode that it sets. + +Note + +In API version 2\.13 and earlier, [`InstrumentContext.speed`](index.html#opentrons.protocol_api.InstrumentContext.speed 'opentrons.protocol_api.InstrumentContext.speed') offered similar functionality to `.flow_rate`. It attempted to set the plunger speed in mm/s. Due to technical limitations, that speed could only be approximate. You must use `.flow_rate` in version 2\.14 and later, and you should consider replacing older code that sets `.speed`. + +New in version 2\.0\. + +##### Flex Pipette Flow Rates + +The default flow rates for Flex pipettes depend on the maximum volume of the pipette and the capacity of the currently attached tip. For each pipette–tip configuration, the default flow rate is the same for aspirate, dispense, and blowout actions. + +| Pipette Model | Tip Capacity (µL) | Flow Rate (µL/s) | +| ----------------------------------- | ----------------- | ---------------- | +| 50 µL (1\- and 8\-channel) | All capacities | 57 | +| 1000 µL (1\-, 8\-, and 96\-channel) | 50 | 478 | +| 1000 µL (1\-, 8\-, and 96\-channel) | 200 | 716 | +| 1000 µL (1\-, 8\-, and 96\-channel) | 1000 | 716 | + +Additionally, all Flex pipettes have a well bottom clearance of 1 mm for aspirate and dispense actions. + +##### OT\-2 Pipette Flow Rates + +The following table provides data on the default aspirate, dispense, and blowout flow rates (in µL/s) for OT\-2 GEN2 pipettes. Default flow rates are the same across all three actions. + +| Pipette Model | Volume (µL) | Flow Rates (µL/s) | +| -------------------------- | ----------- | ----------------------------------------------------------- | +| P20 Single\-Channel GEN2 | 1–20 | _ API v2\.6 or higher: 7\.56 _ API v2\.5 or lower: 3\.78 | +| P300 Single\-Channel GEN2 | 20–300 | _ API v2\.6 or higher: 92\.86 _ API v2\.5 or lower: 46\.43 | +| P1000 Single\-Channel GEN2 | 100–1000 | _ API v2\.6 or higher: 274\.7 _ API v2\.5 or lower: 137\.35 | +| P20 Multi\-Channel GEN2 | 1–20 | 7\.6 | +| P300 Multi\-Channel GEN2 | 20–300 | 94 | + +Additionally, all OT\-2 GEN2 pipettes have a default head speed of 400 mm/s and a well bottom clearance of 1 mm for aspirate and dispense actions. + +#### OT\-2 Pipette Generations + +The OT\-2 works with the GEN1 and GEN2 pipette models. The newer GEN2 pipettes have different volume ranges than the older GEN1 pipettes. With some exceptions, the volume ranges for GEN2 pipettes overlap those used by the GEN1 models. If your protocol specifies a GEN1 pipette, but you have a GEN2 pipette with a compatible volume range, you can still run your protocol. The OT\-2 will consider the GEN2 pipette to have the same minimum volume as the GEN1 pipette. The following table lists the volume compatibility between the GEN2 and GEN1 pipettes. + +| GEN2 Pipette | GEN1 Pipette | GEN1 Volume | +| -------------------------- | -------------------------- | ------------ | +| P20 Single\-Channel GEN2 | P10 Single\-Channel GEN1 | 1\-10 µL | +| P20 Multi\-Channel GEN2 | P10 Multi\-Channel GEN1 | 1\-10 µL | +| P300 Single\-Channel GEN2 | P300 Single\-Channel GEN1 | 30\-300 µL | +| P300 Multi\-Channel GEN2 | P300 Multi\-Channel GEN1 | 20\-200 µL | +| P1000 Single\-Channel GEN2 | P1000 Single\-Channel GEN1 | 100\-1000 µL | + +The single\- and multi\-channel P50 GEN1 pipettes are the exceptions here. If your protocol uses a P50 GEN1 pipette, there is no backward compatibility with a related GEN2 pipette. To replace a P50 GEN1 with a corresponding GEN2 pipette, edit your protocol to load a P20 Single\-Channel GEN2 (for volumes below 20 µL) or a P300 Single\-Channel GEN2 (for volumes between 20 and 50 µL). + +### Partial Tip Pickup + +The 96\-channel pipette occupies both pipette mounts on Flex, so it’s not possible to attach another pipette at the same time. Partial tip pickup lets you perform some of the same actions that you would be able to perform with a second pipette. As of version 2\.16 of the API, you can configure the 96\-channel pipette to pick up a single column of tips, similar to the behavior of an 8\-channel pipette. + +#### Nozzle Layout + +Use the [`configure_nozzle_layout()`](index.html#opentrons.protocol_api.InstrumentContext.configure_nozzle_layout 'opentrons.protocol_api.InstrumentContext.configure_nozzle_layout') method to choose how many tips the 96\-channel pipette will pick up. The method’s `style` parameter accepts special layout constants. You must import these constants at the top of your protocol, or you won’t be able to configure the pipette for partial tip pickup. + +At minimum, import the API from the `opentrons` package: + +``` +from opentrons import protocol_api + +``` + +Then when you call `configure_nozzle_layout` later in your protocol, you can set `style=protocol_api.COLUMN`. + +For greater convenience, also import the individual layout constants that you plan to use in your protocol: + +``` +from opentrons.protocol_api import COLUMN, ALL + +``` + +Then when you call `configure_nozzle_layout` later in your protocol, you can set `style=COLUMN`. + +Here is the start of a protocol that performs both imports, loads a 96\-channel pipette, and sets it to pick up a single column of tips. + +``` +from opentrons import protocol_api +from opentrons.protocol_api import COLUMN, ALL + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + column_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + location="D3" + ) + trash = protocol.load_trash_bin("A3") + pipette = protocol.load_instrument("flex_96channel_1000") + pipette.configure_nozzle_layout( + style=COLUMN, + start="A12", + tip_racks=[column_rack] + ) + +``` + +New in version 2\.16\. + +Let’s unpack some of the details of this code. + +First, we’ve given a special name to the tip rack, `column_rack`. You can name your tip racks whatever you like, but if you’re performing full pickup and partial pickup in the same protocol, you’ll need to keep them separate. See [Tip Rack Adapters](#partial-tip-rack-adapters) below. + +Next, we load the 96\-channel pipette. Note that [`load_instrument()`](index.html#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument') only has a single argument. The 96\-channel pipette occupies both mounts, so `mount` is omissible. The `tip_racks` argument is always optional. But it would have no effect to declare it here, because every call to `configure_nozzle_layout()` resets the pipette’s [`InstrumentContext.tip_racks`](index.html#opentrons.protocol_api.InstrumentContext.tip_racks 'opentrons.protocol_api.InstrumentContext.tip_racks') property. + +Finally, we configure the nozzle layout, with three arguments. + +> - The `style` parameter directly accepts the `COLUMN` constant, since we imported it at the top of the protocol. +> - The `start` parameter accepts a nozzle name, representing the back\-left nozzle in the layout, as a string. `"A12"` tells the pipette to use its rightmost column of nozzles for pipetting. +> - The `tip_racks` parameter tells the pipette which racks to use for tip tracking, just like [adding tip racks](index.html#pipette-tip-racks) when loading a pipette. + +In this configuration, pipetting actions will use a single column: + +``` +# configured in COLUMN mode +pipette.pick_up_tip() # picks up A1-H1 from tip rack +pipette.drop_tip() +pipette.pick_up_tip() # picks up A2-H2 from tip rack + +``` + +Warning + +[`InstrumentContext.pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') always accepts a `location` argument, regardless of nozzle configuration. Do not pass a value that would lead the pipette to line up over more unused tips than specified by the current layout. For example, setting `COLUMN` layout and then calling `pipette.pick_up_tip(tip_rack["A2"])` on a full tip rack will lead to unexpected pipetting behavior and potential crashes. + +#### Tip Rack Adapters + +You can use both partial and full tip pickup in the same protocol. This requires having some tip racks directly on the deck, and some tip racks in the tip rack adapter. + +Do not use a tip rack adapter when performing partial tip pickup. Instead, place the tip rack on the deck. During partial tip pickup, the 96\-channel pipette lowers onto the tip rack in a horizontally offset position. If the tip rack were in its adapter, the pipette would collide with the adapter’s posts, which protrude above the top of the tip rack. If you configure the pipette for partial pickup and then call `pick_up_tip()` on a tip rack that’s loaded onto an adapter, the API will raise an error. + +On the other hand, you must use the tip rack adapter for full tip pickup. If the 96\-channel pipette is in a full layout, either by default or by configuring `style=ALL`, and you then call `pick_up_tip()` on a tip rack that’s not in an adapter, the API will raise an error. + +When switching between full and partial pickup, you may want to organize your tip racks into lists, depending on whether they’re loaded on adapters or not. + +``` +tips_1 = protocol.load_labware( + "opentrons_flex_96_tiprack_1000ul", "C1" +) +tips_2 = protocol.load_labware( + "opentrons_flex_96_tiprack_1000ul", "D1" +) +tips_3 = protocol.load_labware( + "opentrons_flex_96_tiprack_1000ul", "C3", + adapter="opentrons_flex_96_tiprack_adapter" +) +tips_4 = protocol.load_labware( + "opentrons_flex_96_tiprack_1000ul", "D3", + adapter="opentrons_flex_96_tiprack_adapter" +) + +partial_tip_racks = [tips_1, tips_2] +full_tip_racks = [tips_3, tips_4] + +``` + +Tip + +It’s also good practice to keep separate lists of tip racks when using multiple partial tip pickup configurations (i.e., using both column 1 and column 12 in the same protocol). This improves positional accuracy when picking up tips. Additionally, use Labware Position Check in the Opentrons App to ensure that the partial configuration is well\-aligned to the rack. + +Now, when you configure the nozzle layout, you can reference the appropriate list as the value of `tip_racks`: + +``` +pipette.configure_nozzle_layout( + style=COLUMN, + start="A12", + tip_racks=partial_tip_racks +) +# partial pipetting commands go here + +pipette.configure_nozzle_layout( + style=ALL, + tip_racks=full_tip_racks +) +pipette.pick_up_tip() # picks up full rack in C1 + +``` + +This keeps tip tracking consistent across each type of pickup. And it reduces the risk of errors due to the incorrect presence or absence of a tip rack adapter. + +#### Tip Pickup and Conflicts + +During partial tip pickup, 96\-channel pipette moves into spaces above adjacent slots. To avoid crashes, the API prevents you from performing partial tip pickup when there is tall labware in these spaces. The current nozzle layout determines which labware can safely occupy adjacent slots. + +The API will raise errors for potential labware crashes when using a column nozzle configuration. Nevertheless, it’s a good idea to do the following when working with partial tip pickup: + +> - Plan your deck layout carefully. Make a diagram and visualize everywhere the pipette will travel. +> - Simulate your protocol and compare the run preview to your expectations of where the pipette will travel. +> - Perform a dry run with only tip racks on the deck. Have the Emergency Stop Pendant handy in case you see an impending crash. + +For column pickup, Opentrons recommends using the nozzles in column 12 of the pipette: + +``` +pipette.configure_nozzle_layout( + style=COLUMN, + start="A12", +) + +``` + +When using column 12, the pipette overhangs space to the left of wherever it is picking up tips or pipetting. For this reason, it’s a good idea to organize tip racks front to back on the deck. If you place them side by side, the rack to the right will be inaccessible. For example, let’s load three tip racks in the front left corner of the deck: + +``` +tips_C1 = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "C1") +tips_D1 = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "D1") +tips_D2 = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "D2") + +``` + +Now the pipette will be able to access the racks in column 1 only. `pick_up_tip(tips_D2["A1"])` will raise an error due to the tip rack immediately to its left, in slot D1\. There a couple of ways to avoid this error: + +> - Load the tip rack in a different slot, with no tall labware to its left. +> - Use all the tips in slot D1 first, and then use [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') to make space for the pipette before picking up tips from D2\. + +You would get a similar error trying to aspirate from or dispense into a well plate in slot D3, since there is a tip rack to the left. + +Tip + +When using column 12 for partial tip pickup and pipetting, generally organize your deck with the shortest labware on the left side of the deck, and the tallest labware on the right side. + +If your application can’t accommodate a deck layout that works well with column 12, you can configure the 96\-channel pipette to pick up tips with column 1: + +``` +pipette.configure_nozzle_layout( + style=COLUMN, + start="A1", +) + +``` + +Note + +When using a column 1 layout, the pipette can’t reach the rightmost portion of labware in slots A3–D3\. Any well that is within 29 mm of the right edge of the slot may be inaccessible. Use a column 12 layout if you need to pipette in that area. + +### Volume Modes + +The Flex 1\-Channel 50 µL and Flex 8\-Channel 50 µL pipettes must operate in a low\-volume mode to accurately dispense very small volumes of liquid. Set the volume mode by calling [`InstrumentContext.configure_for_volume()`](index.html#opentrons.protocol_api.InstrumentContext.configure_for_volume 'opentrons.protocol_api.InstrumentContext.configure_for_volume') with the amount of liquid you plan to aspirate, in µL: + +``` +pipette50.configure_for_volume(1) +pipette50.pick_up_tip() +pipette50.aspirate(1, plate["A1"]) + +``` + +New in version 2\.15\. + +Passing different values to `configure_for_volume()` changes the minimum and maximum volume of Flex 50 µL pipettes as follows: + +| Value | Minimum Volume (µL) | Maximum Volume (µL) | +| ------ | ------------------- | ------------------- | +| 1–4\.9 | 1 | 30 | +| 5–50 | 5 | 50 | + +Note + +The pipette must not contain liquid when you call `configure_for_volume()`, or the API will raise an error. + +Also, if the pipette is in a well location that may contain liquid, it will move upward to ensure it is not immersed in liquid before changing its mode. Calling `configure_for_volume()` _before_ `pick_up_tip()` helps to avoid this situation. + +In a protocol that handles many different volumes, it’s a good practice to call `configure_for_volume()` once for each [`transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') or [`aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate'), specifying the volume that you are about to handle. When operating with a list of volumes, nest `configure_for_volume()` inside a `for` loop to ensure that the pipette is properly configured for each volume: + +``` +volumes = [1, 2, 3, 4, 1, 5, 2, 8] +sources = plate.columns()[0] +destinations = plate.columns()[1] +for i in range(8): + pipette50.configure_for_volume(volumes[i]) + pipette50.pick_up_tip() + pipette50.aspirate(volume=volumes[i], location=sources[i]) + pipette50.dispense(location=destinations[i]) + pipette50.drop_tip() + +``` + +If you know that all your liquid handling will take place in a specific mode, then you can call `configure_for_volume()` just once with a representative volume. Or if all the volumes correspond to the pipette’s default mode, you don’t have to call `configure_for_volume()` at all. + +Opentrons pipettes are configurable devices used to move liquids throughout the working area during the execution of protocols. Flex and OT\-2 each have their own pipettes, which are available for use in the Python API. + +Pages in this section of the documentation cover: + +> - [Loading pipettes](index.html#loading-pipettes) into your protocol. +> - [Pipette characteristics](index.html#pipette-characteristics), such as how fast they can move liquid and how they move around the deck. +> - The [partial tip pickup](index.html#partial-tip-pickup) configuration for the Flex 96\-Channel Pipette, which uses only 8 channels for pipetting. Full and partial tip pickup can be combined in a single protocol. +> - The [volume modes](index.html#pipette-volume-modes) of Flex 50 µL pipettes, which must operate in low\-volume mode to accurately dispense very small volumes of liquid. + +For information about liquid handling, see [Building Block Commands](index.html#v2-atomic-commands) and [Complex Commands](index.html#v2-complex-commands). + +## Building Block Commands + +### Manipulating Pipette Tips + +Your robot needs to attach a disposable tip to the pipette before it can aspirate or dispense liquids. The API provides three basic functions that help the robot attach and manage pipette tips during a protocol run. These methods are [`InstrumentContext.pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip'), [`InstrumentContext.drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip'), and [`InstrumentContext.return_tip()`](index.html#opentrons.protocol_api.InstrumentContext.return_tip 'opentrons.protocol_api.InstrumentContext.return_tip'). Respectively, these methods tell the robot to pick up a tip from a tip rack, drop a tip into the trash (or another location), and return a tip to its location in the tip rack. + +The following sections demonstrate how to use each method and include sample code. The examples used here assume that you’ve loaded the pipettes and labware from the basic [protocol template](index.html#protocol-template). + +#### Picking Up a Tip + +To pick up a tip, call the [`pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') method without any arguments: + +``` +pipette.pick_up_tip() + +``` + +When added to the protocol template, this simple statement works because the API knows which tip rack is associated with `pipette`, as indicated by `tip_racks=[tiprack_1]` in the [`load_instrument()`](index.html#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument') call. And it knows the on\-deck location of the tip rack (slot D3 on Flex, slot 3 on OT\-2\) from the `location` argument of [`load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware'). Given this information, the robot moves to the tip rack and picks up a tip from position A1 in the rack. On subsequent calls to `pick_up_tip()`, the robot will use the next available tip. For example: + +``` +pipette.pick_up_tip() # picks up tip from rack location A1 +pipette.drop_tip() # drops tip in trash bin +pipette.pick_up_tip() # picks up tip from rack location B1 +pipette.drop_tip() # drops tip in trash bin + +``` + +If you omit the `tip_rack` argument from the `pipette` variable, the API will raise an error. In that case, you must pass the tip rack’s location to `pick_up_tip` like this: + +``` +pipette.pick_up_tip(tiprack_1["A1"]) +pipette.drop_tip() +pipette.pick_up_tip(tiprack_1["B1"]) + +``` + +In most cases, it’s best to associate tip racks with a pipette and let the API automatically track pickup location for you. This also makes it easy to pick up tips when iterating over a loop, as shown in the next section. + +New in version 2\.0\. + +#### Automating Tip Pick Up + +When used with Python’s [`range`](https://docs.python.org/3/library/stdtypes.html#range '(in Python v3.12)') class, a `for` loop brings automation to the tip pickup and tracking process. It also eliminates the need to call `pick_up_tip()` multiple times. For example, this snippet tells the robot to sequentially use all the tips in a 96\-tip rack: + +``` +for i in range(96): + pipette.pick_up_tip() + # liquid handling commands + pipette.drop_tip() + +``` + +If your protocol requires a lot of tips, add a second tip rack to the protocol. Then, associate it with your pipette and increase the number of repetitions in the loop. The robot will work through both racks. + +First, add another tip rack to the sample protocol: + +``` +tiprack_2 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + location="C3" +) + +``` + +Next, change the pipette’s `tip_rack` property to include the additional rack: + +``` +pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=[tiprack_1, tiprack_2], +) + +``` + +Finally, iterate over a larger range: + +``` +for i in range(192): + pipette.pick_up_tip() + # liquid handling commands + pipette.drop_tip() + +``` + +For a more advanced “real\-world” example, review the [off\-deck location protocol](index.html#off-deck-location) on the [Moving Labware](index.html#moving-labware) page. This example also uses a `for` loop to iterate through a tip rack, but it includes other commands that pause the protocol and let you replace an on\-deck tip rack with another rack stored in an off\-deck location. + +#### Dropping a Tip + +To drop a tip in the pipette’s trash container, call the [`drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip') method with no arguments: + +``` +pipette.pick_up_tip() + +``` + +You can specify where to drop the tip by passing in a location. For example, this code drops a tip in the trash bin and returns another tip to to a previously used well in a tip rack: + +``` +pipette.pick_up_tip() # picks up tip from rack location A1 +pipette.drop_tip() # drops tip in default trash container +pipette.pick_up_tip() # picks up tip from rack location B1 +pipette.drop_tip(tiprack["A1"]) # drops tip in rack location A1 + +``` + +New in version 2\.0\. + +Another use of the `location` parameter is to drop a tip in a specific trash container. For example, calling `pipette.drop_tip(chute)` will dispose tips in the waste chute, even if the pipette’s default trash container is a trash bin: + +``` +pipette.pick_up_tip() # picks up tip from rack location A1 +pipette.drop_tip() # drops tip in default trash container +pipette.pick_up_tip() # picks up tip from rack location B1 +pipette.drop_tip(chute) # drops tip in waste chute + +``` + +New in version 2\.16\. + +#### Returning a Tip + +To return a tip to its original location, call the [`return_tip()`](index.html#opentrons.protocol_api.InstrumentContext.return_tip 'opentrons.protocol_api.InstrumentContext.return_tip') method with no arguments: + +``` +pipette.return_tip() + +``` + +New in version 2\.0\. + +Note + +You can’t return tips with a pipette that’s configured to use [partial tip pickup](index.html#partial-tip-pickup). This restriction ensures that the pipette has clear access to unused tips. For example, a 96\-channel pipette in column configuration can’t reach column 2 unless column 1 is empty. + +If you call `return_tip()` while using partial tip pickup, the API will raise an error. Use `drop_tip()` to dispose the tips instead. + +#### Working With Used Tips + +Currently, the API considers tips as “used” after being picked up. For example, if the robot picked up a tip from rack location A1 and then returned it to the same location, it will not attempt to pick up this tip again, unless explicitly specified. Instead, the robot will pick up a tip starting from rack location B1\. For example: + +``` +pipette.pick_up_tip() # picks up tip from rack location A1 +pipette.return_tip() # drops tip in rack location A1 +pipette.pick_up_tip() # picks up tip from rack location B1 +pipette.drop_tip() # drops tip in trash bin +pipette.pick_up_tip(tiprack_1["A1"]) # picks up tip from rack location A1 + +``` + +Early API versions treated returned tips as unused items. They could be picked up again without an explicit argument. For example: + +``` +pipette.pick_up_tip() # picks up tip from rack location A1 +pipette.return_tip() # drops tip in rack location A1 +pipette.pick_up_tip() # picks up tip from rack location A1 + +``` + +Changed in version 2\.2\. + +### Liquid Control + +After attaching a tip, your robot is ready to aspirate, dispense, and perform other liquid handling tasks. The API includes methods that help you perform these actions and the following sections show how to use them. The examples used here assume that you’ve loaded the pipettes and labware from the basic [protocol template](index.html#protocol-template). + +#### Aspirate + +To draw liquid up into a pipette tip, call the [`InstrumentContext.aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') method. Using this method, you can specify the aspiration volume in µL, the well location, and pipette flow rate. Other parameters let you position the pipette within a well. For example, this snippet tells the robot to aspirate 200 µL from well location A1\. + +``` +pipette.pick_up_tip() +pipette.aspirate(200, plate["A1"]) + +``` + +If the pipette doesn’t move, you can specify an additional aspiration action without including a location. To demonstrate, this code snippet pauses the protocol, automatically resumes it, and aspirates a second time from `plate["A1"]`). + +``` +pipette.pick_up_tip() +pipette.aspirate(200, plate["A1"]) +protocol.delay(seconds=5) # pause for 5 seconds +pipette.aspirate(100) # aspirate 100 µL at current position + +``` + +Now our pipette holds 300 µL. + +##### Aspirate by Well or Location + +The [`aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') method includes a `location` parameter that accepts either a [`Well`](index.html#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') or a [`Location`](index.html#opentrons.types.Location 'opentrons.types.Location'). + +If you specify a well, like `plate["A1"]`, the pipette will aspirate from a default position 1 mm above the bottom center of that well. To change the default clearance, first set the `aspirate` attribute of [`well_bottom_clearance`](index.html#opentrons.protocol_api.InstrumentContext.well_bottom_clearance 'opentrons.protocol_api.InstrumentContext.well_bottom_clearance'): + +``` +pipette.pick_up_tip +pipette.well_bottom_clearance.aspirate = 2 # tip is 2 mm above well bottom +pipette.aspirate(200, plate["A1"]) + +``` + +You can also aspirate from a location along the center vertical axis within a well using the [`Well.top()`](index.html#opentrons.protocol_api.Well.top 'opentrons.protocol_api.Well.top') and [`Well.bottom()`](index.html#opentrons.protocol_api.Well.bottom 'opentrons.protocol_api.Well.bottom') methods. These methods move the pipette to a specified distance relative to the top or bottom center of a well: + +``` +pipette.pick_up_tip() +depth = plate["A1"].bottom(z=2) # tip is 2 mm above well bottom +pipette.aspirate(200, depth) + +``` + +See also: + +- [Default Positions](index.html#new-default-op-positions) for information about controlling pipette height for a particular pipette. +- [Position Relative to Labware](index.html#position-relative-labware) for information about controlling pipette height from within a well. +- [Move To](index.html#move-to) for information about moving a pipette to any reachable deck location. + +##### Aspiration Flow Rates + +Flex and OT\-2 pipettes aspirate at [default flow rates](index.html#new-plunger-flow-rates) measured in µL/s. Specifying the `rate` parameter multiplies the flow rate by that value. As a best practice, don’t set the flow rate higher than 3x the default. For example, this code causes the pipette to aspirate at twice its normal rate: + +``` +pipette.aspirate(200, plate["A1"], rate=2.0) + +``` + +New in version 2\.0\. + +#### Dispense + +To dispense liquid from a pipette tip, call the [`InstrumentContext.dispense()`](index.html#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') method. Using this method, you can specify the dispense volume in µL, the well location, and pipette flow rate. Other parameters let you position the pipette within a well. For example, this snippet tells the robot to dispense 200 µL into well location B1\. + +``` +pipette.dispense(200, plate["B1"]) + +``` + +Note + +In API version 2\.16 and earlier, you could pass a `volume` argument to `dispense()` greater than what was aspirated into the pipette. In this case, the API would ignore `volume` and dispense the pipette’s [`current_volume`](index.html#opentrons.protocol_api.InstrumentContext.current_volume 'opentrons.protocol_api.InstrumentContext.current_volume'). The robot _would not_ move the plunger lower as a result. + +In version 2\.17 and later, passing such values raises an error. + +To move the plunger a small extra amount, add a [push out](#push-out-dispense). Or to move it a large amount, use [blow out](#blow-out). + +If the pipette doesn’t move, you can specify an additional dispense action without including a location. To demonstrate, this code snippet pauses the protocol, automatically resumes it, and dispense a second time from location B1\. + +``` +pipette.dispense(100, plate["B1"]) +protocol.delay(seconds=5) # pause for 5 seconds +pipette.dispense(100) # dispense 100 µL at current position + +``` + +##### Dispense by Well or Location + +The [`dispense()`](index.html#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') method includes a `location` parameter that accepts either a [`Well`](index.html#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') or a [`Location`](index.html#opentrons.types.Location 'opentrons.types.Location'). + +If you specify a well, like `plate["B1"]`, the pipette will dispense from a default position 1 mm above the bottom center of that well. To change the default clearance, you would call [`well_bottom_clearance`](index.html#opentrons.protocol_api.InstrumentContext.well_bottom_clearance 'opentrons.protocol_api.InstrumentContext.well_bottom_clearance'): + +``` +pipette.well_bottom_clearance.dispense=2 # tip is 2 mm above well bottom +pipette.dispense(200, plate["B1"]) + +``` + +You can also dispense from a location along the center vertical axis within a well using the [`Well.top()`](index.html#opentrons.protocol_api.Well.top 'opentrons.protocol_api.Well.top') and [`Well.bottom()`](index.html#opentrons.protocol_api.Well.bottom 'opentrons.protocol_api.Well.bottom') methods. These methods move the pipette to a specified distance relative to the top or bottom center of a well: + +``` +depth = plate["B1"].bottom(z=2) # tip is 2 mm above well bottom +pipette.dispense(200, depth) + +``` + +See also: + +- [Default Positions](index.html#new-default-op-positions) for information about controlling pipette height for a particular pipette. +- [Position Relative to Labware](index.html#position-relative-labware) for formation about controlling pipette height from within a well. +- [Move To](index.html#move-to) for information about moving a pipette to any reachable deck location. + +##### Dispense Flow Rates + +Flex and OT\-2 pipettes dispense at [default flow rates](index.html#new-plunger-flow-rates) measured in µL/s. Adding a number to the `rate` parameter multiplies the flow rate by that value. As a best practice, don’t set the flow rate higher than 3x the default. For example, this code causes the pipette to dispense at twice its normal rate: + +``` +pipette.dispense(200, plate["B1"], rate=2.0) + +``` + +New in version 2\.0\. + +##### Push Out After Dispense + +The optional `push_out` parameter of `dispense()` helps ensure all liquid leaves the tip. Use `push_out` for applications that require moving the pipette plunger lower than the default, without performing a full [blow out](#blow-out). + +For example, this dispense action moves the plunger the equivalent of an additional 5 µL beyond where it would stop if `push_out` was set to zero or omitted: + +``` +pipette.pick_up_tip() +pipette.aspirate(100, plate["A1"]) +pipette.dispense(100, plate["B1"], push_out=5) +pipette.drop_tip() + +``` + +New in version 2\.15\. + +#### Blow Out + +To blow an extra amount of air through the pipette’s tip, call the [`InstrumentContext.blow_out()`](index.html#opentrons.protocol_api.InstrumentContext.blow_out 'opentrons.protocol_api.InstrumentContext.blow_out') method. You can use a specific well in a well plate or reservoir as the blowout location. If no location is specified, the pipette will blowout from its current well position: + +``` +pipette.blow_out() + +``` + +You can also specify a particular well as the blowout location: + +``` +pipette.blow_out(plate["B1"]) + +``` + +Many protocols use a trash container for blowing out the pipette. You can specify the pipette’s current trash container as the blowout location by using the [`InstrumentContext.trash_container`](index.html#opentrons.protocol_api.InstrumentContext.trash_container 'opentrons.protocol_api.InstrumentContext.trash_container') property: + +``` +pipette.blow_out(pipette.trash_container) + +``` + +New in version 2\.0\. + +Changed in version 2\.16: Added support for `TrashBin` and `WasteChute` locations. + +#### Touch Tip + +The [`InstrumentContext.touch_tip()`](index.html#opentrons.protocol_api.InstrumentContext.touch_tip 'opentrons.protocol_api.InstrumentContext.touch_tip') method moves the pipette so the tip touches each wall of a well. A touch tip procedure helps knock off any droplets that might cling to the pipette’s tip. This method includes optional arguments that allow you to control where the tip will touch the inner walls of a well and the touch speed. Calling [`touch_tip()`](index.html#opentrons.protocol_api.InstrumentContext.touch_tip 'opentrons.protocol_api.InstrumentContext.touch_tip') without arguments causes the pipette to touch the well walls from its current location: + +``` +pipette.touch_tip() + +``` + +##### Touch Location + +These optional location arguments give you control over where the tip will touch the side of a well. + +This example demonstrates touching the tip in a specific well: + +``` +pipette.touch_tip(plate["B1"]) + +``` + +This example uses an offset to set the touch tip location 2mm below the top of the current well: + +``` +pipette.touch_tip(v_offset=-2) + +``` + +This example moves the pipette 75% of well’s total radius and 2 mm below the top of well: + +``` +pipette.touch_tip(plate["B1"], + radius=0.75, + v_offset=-2) + +``` + +The `touch_tip` feature allows the pipette to touch the edges of a well gently instead of crashing into them. It includes the `radius` argument. When `radius=1` the robot moves the centerline of the pipette’s plunger axis to the edge of a well. This means a pipette tip may sometimes touch the well wall too early, causing it to bend inwards. A smaller radius helps avoid premature wall collisions and a lower speed produces gentler motion. Different liquid droplets behave differently, so test out these parameters in a single well before performing a full protocol run. + +Warning + +_Do not_ set the `radius` value greater than `1.0`. When `radius` is \> `1.0`, the robot will forcibly move the pipette tip across a well wall or edge. This type of aggressive movement can damage the pipette tip and the pipette. + +##### Touch Speed + +Touch speed controls how fast the pipette moves in mm/s during a touch tip step. The default movement speed is 60 mm/s, the minimum is 1 mm/s, and the maximum is 80 mm/s. Calling `touch_tip` without any arguments moves a tip at the default speed in the current well: + +``` +pipette.touch_tip() + +``` + +This example specifies a well location and sets the speed to 20 mm/s: + +``` +pipette.touch_tip(plate["B1"], speed=20) + +``` + +This example uses the current well and sets the speed to 80 mm/s: + +``` +pipette.touch_tip(speed=80) + +``` + +New in version 2\.0\. + +Changed in version 2\.4: Lowered minimum speed to 1 mm/s. + +#### Mix + +The [`mix()`](index.html#opentrons.protocol_api.InstrumentContext.mix 'opentrons.protocol_api.InstrumentContext.mix') method aspirates and dispenses repeatedly in a single location. It’s designed to mix the contents of a well together using a single command rather than using multiple `aspirate()` and `dispense()` calls. This method includes arguments that let you specify the number of times to mix, the volume (in µL) of liquid, and the well that contains the liquid you want to mix. + +This example draws 100 µL from the current well and mixes it three times: + +``` +pipette.mix(repetitions=3, volume=100) + +``` + +This example draws 100 µL from well B1 and mixes it three times: + +``` +pipette.mix(3, 100, plate["B1"]) + +``` + +This example draws an amount equal to the pipette’s maximum rated volume and mixes it three times: + +``` +pipette.mix(repetitions=3) + +``` + +Note + +In API versions 2\.2 and earlier, during a mix, the pipette moves up and out of the target well. In API versions 2\.3 and later, the pipette does not move while mixing. + +New in version 2\.0\. + +#### Air Gap + +The [`InstrumentContext.air_gap()`](index.html#opentrons.protocol_api.InstrumentContext.air_gap 'opentrons.protocol_api.InstrumentContext.air_gap') method tells the pipette to draw in air before or after a liquid. Creating an air gap helps keep liquids from seeping out of a pipette after drawing it from a well. This method includes arguments that give you control over the amount of air to aspirate and the pipette’s height (in mm) above the well. By default, the pipette moves 5 mm above a well before aspirating air. Calling [`air_gap()`](index.html#opentrons.protocol_api.InstrumentContext.air_gap 'opentrons.protocol_api.InstrumentContext.air_gap') with no arguments uses the entire remaining volume in the pipette. + +This example aspirates 200 µL of air 5 mm above the current well: + +``` +pipette.air_gap(volume=200) + +``` + +This example aspirates 200 µL of air 20 mm above the the current well: + +``` +pipette.air_gap(volume=200, height=20) + +``` + +This example aspirates enough air to fill the remaining volume in a pipette: + +``` +pipette.air_gap() + +``` + +New in version 2\.0\. + +### Utility Commands + +With utility commands, you can control various robot functions such as pausing or delaying a protocol, checking the robot’s door, turning robot lights on/off, and more. The following sections show you how to these utility commands and include sample code. The examples used here assume that you’ve loaded the pipettes and labware from the basic [protocol template](index.html#protocol-template). + +#### Delay and Resume + +Call the [`ProtocolContext.delay()`](index.html#opentrons.protocol_api.ProtocolContext.delay 'opentrons.protocol_api.ProtocolContext.delay') method to insert a timed delay into your protocol. This method accepts time increments in seconds, minutes, or combinations of both. Your protocol resumes automatically after the specified time expires. + +This example delays a protocol for 10 seconds: + +``` +protocol.delay(seconds=10) + +``` + +This example delays a protocol for 5 minutes: + +``` +protocol.delay(minutes=5) + +``` + +This example delays a protocol for 5 minutes and 10 seconds: + +``` +protocol.delay(minutes=5, seconds=10) + +``` + +#### Pause Until Resumed + +Call the [`ProtocolContext.pause()`](index.html#opentrons.protocol_api.ProtocolContext.pause 'opentrons.protocol_api.ProtocolContext.pause') method to stop a protocol at a specific step. Unlike a delay, [`pause()`](index.html#opentrons.protocol_api.ProtocolContext.pause 'opentrons.protocol_api.ProtocolContext.pause') does not restart your protocol automatically. To resume, you’ll respond to a prompt on the touchscreen or in the Opentrons App. This method also lets you specify an optional message that provides on\-screen or in\-app instructions on how to proceed. This example inserts a pause and includes a brief message: + +``` +protocol.pause("Remember to get more pipette tips") + +``` + +New in version 2\.0\. + +#### Homing + +Homing commands the robot to move the gantry, a pipette, or a pipette plunger to a defined position. For example, homing the gantry moves it to the back right of the working area. With the available homing methods you can home the gantry, home the mounted pipette and plunger, and home the pipette plunger. These functions take no arguments. + +To home the gantry, call [`ProtocolContext.home()`](index.html#opentrons.protocol_api.ProtocolContext.home 'opentrons.protocol_api.ProtocolContext.home'): + +``` +protocol.home() + +``` + +To home a specific pipette’s Z axis and plunger, call [`InstrumentContext.home()`](index.html#opentrons.protocol_api.InstrumentContext.home 'opentrons.protocol_api.InstrumentContext.home'): + +``` +pipette = protocol.load_instrument("flex_1channel_1000", "right") +pipette.home() + +``` + +To home a specific pipette’s plunger only, you can call [`InstrumentContext.home_plunger()`](index.html#opentrons.protocol_api.InstrumentContext.home_plunger 'opentrons.protocol_api.InstrumentContext.home_plunger'): + +``` +pipette = protocol.load_instrument("flex_1channel_1000", "right") +pipette.home_plunger() + +``` + +New in version 2\.0\. + +#### Comment + +Call the [`ProtocolContext.comment()`](index.html#opentrons.protocol_api.ProtocolContext.comment 'opentrons.protocol_api.ProtocolContext.comment') method if you want to write and display a brief message in the Opentrons App during a protocol run: + +``` +protocol.comment("Hello, world!") + +``` + +New in version 2\.0\. + +#### Control and Monitor Robot Rail Lights + +Call the [`ProtocolContext.set_rail_lights()`](index.html#opentrons.protocol_api.ProtocolContext.set_rail_lights 'opentrons.protocol_api.ProtocolContext.set_rail_lights') method to turn the robot’s rail lights on or off during a protocol. This method accepts Boolean `True` (lights on) or `False` (lights off) arguments. Rail lights are off by default. + +This example turns the rail lights on: + +``` +protocol.set_rail_lights(True) + +``` + +This example turns the rail lights off: + +``` +protocol.set_rail_lights(False) + +``` + +New in version 2\.5\. + +You can also check whether the rail lights are on or off in the protocol by using [`ProtocolContext.rail_lights_on`](index.html#opentrons.protocol_api.ProtocolContext.rail_lights_on 'opentrons.protocol_api.ProtocolContext.rail_lights_on'). This method returns `True` when lights are on and `False` when the lights are off. + +New in version 2\.5\. + +#### OT\-2 Door Safety Switch + +Introduced with [robot software version](index.html#version-table) 3\.19, the safety switch feature prevents the OT\-2, and your protocol, from running if the door is open. To operate properly, the front door and top window of your OT\-2 must be closed. You can toggle the door safety switch on or off from **Robot Settings \> Advanced \> Usage Settings**. + +To check if the robot’s door is closed at a specific point during a protocol run, call [`ProtocolContext.door_closed`](index.html#opentrons.protocol_api.ProtocolContext.door_closed 'opentrons.protocol_api.ProtocolContext.door_closed'). It returns a Boolean `True` (door closed) or `False` (door open) response. + +``` +protocol.door_closed + +``` + +Warning + +[`door_closed`](index.html#opentrons.protocol_api.ProtocolContext.door_closed 'opentrons.protocol_api.ProtocolContext.door_closed') is a status check only. It does not control the robot’s behavior. If you wish to implement a custom method to pause or resume a protocol using `door_closed`, disable the door safety feature first (not recommended). + +New in version 2\.5\. + +Building block commands execute some of the most basic actions that your robot can complete. But basic doesn’t mean these commands lack capabilities. They perform important tasks in your protocols. They’re also foundational to the [complex commands](index.html#v2-complex-commands) that help you combine multiple actions into fewer lines of code. + +Pages in this section of the documentation cover: + +- [Manipulating Pipette Tips](index.html#pipette-tips): Get started with commands for picking up pipette tips, dropping tips, returning tips, and working with used tips. +- [Liquid Control](index.html#liquid-control): Learn about aspirating and dispensing liquids, blow out and touch tip procedures, mixing, and creating air gaps. +- [Utility Commands](index.html#new-utility-commands): Control various robot functions such as pausing or delaying a protocol, checking the robot’s door, turning robot lights on/off, and more. + +## Complex Commands + +### Sources and Destinations + +The [`InstrumentContext.transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer'), [`InstrumentContext.distribute()`](index.html#opentrons.protocol_api.InstrumentContext.distribute 'opentrons.protocol_api.InstrumentContext.distribute'), and [`InstrumentContext.consolidate()`](index.html#opentrons.protocol_api.InstrumentContext.consolidate 'opentrons.protocol_api.InstrumentContext.consolidate') methods form the family of complex liquid handling commands. These methods require `source` and `dest` (destination) arguments to move liquid from one well, or group of wells, to another. In contrast, the [building block commands](index.html#v2-atomic-commands) [`aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') and [`dispense()`](index.html#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') only operate in a single location. + +For example, this command performs a simple transfer between two wells on a plate: + +``` +pipette.transfer( + volume=100, + source=plate["A1"], + dest=plate["A2"], +) + +``` + +New in version 2\.0\. + +This page covers the restrictions on sources and destinations for complex commands, their different patterns of aspirating and dispensing, and how to optimize them for different use cases. + +#### Source and Destination Arguments + +As noted above, the [`transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer'), [`distribute()`](index.html#opentrons.protocol_api.InstrumentContext.distribute 'opentrons.protocol_api.InstrumentContext.distribute'), and [`consolidate()`](index.html#opentrons.protocol_api.InstrumentContext.consolidate 'opentrons.protocol_api.InstrumentContext.consolidate') methods require `source` and `dest` (destination) arguments to aspirate and dispense liquid. However, each method handles liquid sources and destinations differently. Understanding how complex commands work with source and destination wells is essential to using these methods effectively. + +[`transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') is the most versatile complex liquid handling function, because it has the fewest restrictions on what wells it can operate on. You will likely use transfer commands in many of your protocols. + +Certain liquid handling cases focus on moving liquid to or from a single well. [`distribute()`](index.html#opentrons.protocol_api.InstrumentContext.distribute 'opentrons.protocol_api.InstrumentContext.distribute') limits its source to a single well, while [`consolidate()`](index.html#opentrons.protocol_api.InstrumentContext.consolidate 'opentrons.protocol_api.InstrumentContext.consolidate') limits its destination to a single well. Distribute commands also make changes to liquid\-handling behavior to improve the accuracy of dispensing. + +The following table summarizes the source and destination restrictions for each method. + +| Method | Accepted wells | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `transfer()` | _ **Source:** Any number of wells. _ **Destination:** Any number of wells. \* The larger group of wells must be evenly divisible by the smaller group. | +| `distribute()` | _ **Source:** Exactly one well. _ **Destination:** Any number of wells. | +| `consolidate()` | _ **Source:** Any number of wells. _ **Destination:** Exactly one well. | + +A single well can be passed by itself or as a list with one item: `source=plate["A1"]` and `source=[plate["A1"]]` are equivalent. + +The section on [many\-to\-many transfers](#many-to-many) below covers how `transfer()` works when specifying sources and destinations of different sizes. However, if they don’t meet the even divisibility requirement, the API will raise an error. You can work around such situations by making multiple calls to `transfer()` in sequence or by using a [list of volumes](index.html#complex-list-volumes) to skip certain wells. + +For distributing and consolidating, the API will not raise an error if you use a list of wells as the argument that is limited to exactly one well. Instead, the API will ignore everything except the first well in the list. For example, the following command will only aspirate from well A1: + +``` +pipette.distribute( + volume=100, + source=[plate["A1"], plate["A2"]], # A2 ignored + dest=plate.columns()[1], +) + +``` + +On the other hand, a transfer command with the same arguments would aspirate from both A1 and A2\. The next section examines the exact order of aspiration and dispensing for all three methods. + +#### Transfer Patterns + +Each complex command uses a different pattern of aspiration and dispensing. In addition, when you provide multiple wells as both the source and destination for `transfer()`, it maps the source list onto the destination list in a certain way. + +##### Aspirating and Dispensing + +`transfer()` always alternates between aspirating and dispensing, regardless of how many wells are in the source and destination. Its default behavior is: + +> 1. Pick up a tip. +> 2. Aspirate from the first source well. +> 3. Dispense in the first destination well. +> 4. Repeat the pattern of aspirating and dispensing, as needed. +> 5. Drop the tip in the trash. + +This transfer aspirates six times and dispenses six times. + +`distribute()` always fills the tip with as few aspirations as possible, and then dispenses to the destination wells in order. Its default behavior is: + +> 1. Pick up a tip. +> 2. Aspirate enough to dispense in all the destination wells. This aspirate includes a disposal volume. +> 3. Dispense in the first destination well. +> 4. Continue to dispense in destination wells. +> 5. Drop the tip in the trash. + +See [Tip Refilling](index.html#complex-tip-refilling) below for cases where the total amount to be dispensed is greater than the capacity of the tip. + +This distribute aspirates one time and dispenses three times. + +`consolidate()` aspirates multiple times in a row, and then dispenses as few times as possible in the destination well. Its default behavior is: + +> 1. Pick up a tip. +> 2. Aspirate from the first source well. +> 3. Continue aspirating from source wells. +> 4. Dispense in the destination well. +> 5. Drop the tip in the trash. + +See [Tip Refilling](index.html#complex-tip-refilling) below for cases where the total amount to be aspirated is greater than the capacity of the tip. + +This consolidate aspirates three times and dispenses one time. + +Note + +By default, all three commands begin by picking up a tip and conclude by dropping a tip. In general, don’t call [`pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') just before a complex command, or the API will raise an error. You can override this behavior with the [tip handling complex parameter](index.html#param-tip-handling), by setting `new_tip="never"`. + +##### Many\-to\-Many + +`transfer()` lets you specify both `source` and `dest` arguments that contain multiple wells. This section covers how the method determines which wells to aspirate from and dispense to in these cases. + +When the source and destination both contain the same number of wells, the mapping between wells is straightforward. You can imagine writing out the two lists one above each other, with each unique well in the source list paired to a unique well in the destination list. For example, here is the code for using one row as the source and another row as the destination, and the resulting correspondence between wells: + +``` +pipette.transfer( + volume=50, + source=plate.rows()[0], + dest=plate.rows()[1], +) + +``` + +| Source | A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | +| ----------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Destination | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 | B11 | B12 | + +There’s no requirement that the source and destination lists be mutually exclusive. In fact, this command adapted from the [tutorial](index.html#tutorial) deliberately uses slices of the same list, saved to the variable `row`, with the effect that each aspiration happens in the same location as the previous dispense: + +``` +row = plate.rows()[0] +pipette.transfer( + volume=50, + source=row[:11], + dest=row[1:], +) + +``` + +| Source | A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | +| ----------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Destination | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | + +When the source and destination lists contain different numbers of wells, `transfer()` will always aspirate and dispense as many times as there are wells in the _longer_ list. The shorter list will be “stretched” to cover the length of the longer list. Here is an example of transferring from 3 wells to a full row of 12 wells: + +``` +pipette.transfer( + volume=50, + source=[plate["A1"], plate["A2"], plate["A3"]], + dest=plate.rows()[1], +) + +``` + +| Source | A1 | A1 | A1 | A1 | A2 | A2 | A2 | A2 | A3 | A3 | A3 | A3 | +| ----------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Destination | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 | B11 | B12 | + +This is why the longer list must be evenly divisible by the shorter list. Changing the destination in this example to a column instead of a row will cause the API to raise an error, because 8 is not evenly divisible by 3: + +``` +pipette.transfer( + volume=50, + source=[plate["A1"], plate["A2"], plate["A3"]], + dest=plate.columns()[3], # labware column 4 +) +# error: source and destination lists must be divisible + +``` + +The API raises this error rather than presuming which wells to aspirate from three times and which only two times. If you want to aspirate three times from A1, three times from A2, and two times from A3, use multiple `transfer()` commands in sequence: + +``` +pipette.transfer(50, plate["A1"], plate.columns()[3][:3]) +pipette.transfer(50, plate["A2"], plate.columns()[3][3:6]) +pipette.transfer(50, plate["A3"], plate.columns()[3][6:]) + +``` + +Finally, be aware of the ordering of source and destination lists when constructing them with [well accessor methods](index.html#well-accessor-methods). For example, at first glance this code may appear to take liquid from each well in the first row of a plate and move it to each of the other wells in the same column: + +``` +pipette.transfer( + volume=20, + source=plate.rows()[0], + dest=plate.rows()[1:], +) + +``` + +However, because the well ordering of [`Labware.rows()`](index.html#opentrons.protocol_api.Labware.rows 'opentrons.protocol_api.Labware.rows') goes _across_ the plate instead of _down_ the plate, liquid from A1 will be dispensed in B1–B7, liquid from A2 will be dispensed in B8–C2, etc. The intended task is probably better accomplished by repeating transfers in a `for` loop: + +``` +for i in range(12): + pipette.transfer( + volume=20, + source=plate.rows()[0][i], + dest=plate.columns()[i][1:], + ) + +``` + +Here the repeat index `i` picks out: + +> - The individual well in the first row, for the source. +> - The corresponding column, which is sliced to form the destination. + +##### Optimizing Patterns + +Choosing the right complex command optimizes gantry movement and helps save time in your protocol. For example, say you want to take liquid from a reservoir and put 50 µL in each well of the first row of a plate. You could use `transfer()`, like this: + +``` +pipette.transfer( + volume=50, + source=reservoir["A1"], + destination=plate.rows()[0], +) + +``` + +This will produce 12 aspirate steps and 12 dispense steps. The steps alternate, with the pipette moving back and forth between the reservoir and plate each time. Using `distribute()` with the same arguments is more optimal in this scenario: + +``` +pipette.distribute( + volume=50, + source=reservoir["A1"], + destination=plate.rows()[0], +) + +``` + +This will produce _just 1_ aspirate step and 12 dispense steps (when using a 1000 µL pipette). The pipette will aspirate enough liquid to fill all the wells, plus a disposal volume. Then it will move to A1 of the plate, dispense, move the short distance to A2, dispense, and so on. This greatly reduces gantry movement and the time to perform this action. And even if you’re using a smaller pipette, `distribute()` will fill the pipette, dispense as many times as possible, and only then return to the reservoir to refill (see [Tip Refilling](index.html#complex-tip-refilling) for more information). + +### Order of Operations + +Complex commands perform a series of [building block commands](index.html#v2-atomic-commands) in order. In fact, the run preview for your protocol in the Opentrons App lists all of these commands as separate steps. This lets you examine what effect your complex commands will have before running them. + +This page describes what steps you should expect the robot to perform when using different complex commands with different required and [optional](index.html#complex-params) parameters. + +#### Step Sequence + +The order of steps is fixed within complex commands. Aspiration and dispensing are the only required actions. You can enable or disable all of the other actions with [complex liquid handling parameters](index.html#complex-params). A complex command designed to perform every possible action will proceed in this order: + +> 1. Pick up tip +> 2. Mix at source +> 3. Aspirate from source +> 4. Touch tip at source +> 5. Air gap +> 6. Dispense into destination +> 7. Mix at destination +> 8. Touch tip at destination +> 9. Blow out +> 10. Drop tip + +The command may repeat some or all of these steps in order to move liquid as requested. [`transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') repeats as many times as there are wells in the longer of its `source` or `dest` argument. [`distribute()`](index.html#opentrons.protocol_api.InstrumentContext.distribute 'opentrons.protocol_api.InstrumentContext.distribute') and [`consolidate()`](index.html#opentrons.protocol_api.InstrumentContext.consolidate 'opentrons.protocol_api.InstrumentContext.consolidate') try to repeat as few times as possible. See [Tip Refilling](#complex-tip-refilling) below for how they behave when they do need to repeat. + +#### Example Orders + +The smallest possible number of steps in a complex command is just two: aspirating and dispensing. This is possible by omitting the tip pickup and drop steps: + +``` +pipette.transfer( + volume=100, + source=plate["A1"], + dest=plate["B1"], + new_tip="never", +) + +``` + +Here’s another example, a distribute command that adds touch tip steps (and does not turn off tip handling). The code for this command is: + +``` +pipette.distribute( + volume=100, + source=[plate["A1"]], + dest=[plate["B1"], plate["B2"]], + touch_tip=True, +) + +``` + +Compared to the list of all possible actions, this code will only perform the following: + +> 1. Pick up tip +> 2. Aspirate from source +> 3. Touch tip at source +> 4. Dispense into destination +> 5. Touch tip at destination +> 6. Blow out +> 7. Drop tip + +Let’s unpack this. Picking up and dropping tips is default behavior for `distribute()`. Specifying `touch_tip=True` adds two steps, as it is performed at both the source and destination. And it’s also default behavior for `distribute()` to aspirate a disposal volume, which is blown out before dropping the tip. The exact order of steps in the run preview should look similar to this: + +``` +Picking up tip from A1 of tip rack on 3 +Aspirating 220.0 uL from A1 of well plate on 2 at 92.86 uL/sec +Touching tip +Dispensing 100.0 uL into B1 of well plate on 2 at 92.86 uL/sec +Touching tip +Dispensing 100.0 uL into B2 of well plate on 2 at 92.86 uL/sec +Touching tip +Blowing out at A1 of Opentrons Fixed Trash on 12 +Dropping tip into A1 of Opentrons Fixed Trash on 12 + +``` + +Since dispensing and touching the tip are both associated with the destination wells, those steps are performed at each of the two destination wells. + +#### Tip Refilling + +One factor that affects the exact order of steps for a complex command is whether the amount of liquid being moved can fit in the tip at once. If it won’t fit, you don’t have to adjust your command. The API will handle it for you by including additional steps to refill the tip when needed. + +For example, say you need to move 100 µL of liquid from one well to another, but you only have a 50 µL pipette attached to your robot. To accomplish this with building block commands, you’d need multiple aspirates and dispenses. `aspirate(volume=100)` would raise an error, since it exceeds the tip’s volume. But you can accomplish this with a single transfer command: + +``` +pipette50.transfer( + volume=100, + source=plate["A1"], + dest=plate["B1"], +) + +``` + +To effect the transfer, the API will aspirate and dispense the maximum volume of the pipette (50 µL) twice: + +``` +Picking up tip from A1 of tip rack on D3 +Aspirating 50.0 uL from A1 of well plate on D2 at 57 uL/sec +Dispensing 50.0 uL into B1 of well plate on D2 at 57 uL/sec +Aspirating 50.0 uL from A1 of well plate on D2 at 57 uL/sec +Dispensing 50.0 uL into B1 of well plate on D2 at 57 uL/sec +Dropping tip into A1 of Opentrons Fixed Trash on A3 + +``` + +You can change `volume` to any value (above the minimum volume of the pipette) and the API will automatically calculate how many times the pipette needs to aspirate and dispense. `volume=50` would require just one repetition. `volume=75` would require two, split into 50 µL and 25 µL. `volume=1000` would repeat 20 times — not very efficient, but perhaps more useful than having to swap to a different pipette! + +Remember that `distribute()` includes a disposal volume by default, and this can affect the number of times the pipette refills its tip. Say you want to distribute 80 µL to each of the 12 wells in row A of a plate. That’s 960 µL total — less than the capacity of the pipette — but the 100 µL disposal volume will cause the pipette to refill. + +``` +Picking up tip from A1 of tip rack on 3 +Aspirating 980.0 uL from A1 of well plate on 2 at 274.7 uL/sec +Dispensing 80.0 uL into B1 of well plate on 2 at 274.7 uL/sec +Dispensing 80.0 uL into B2 of well plate on 2 at 274.7 uL/sec +... +Dispensing 80.0 uL into B11 of well plate on 2 at 274.7 uL/sec +Blowing out at A1 of Opentrons Fixed Trash on 12 +Aspirating 180.0 uL from A1 of well plate on 2 at 274.7 uL/sec +Dispensing 80.0 uL into B12 of well plate on 2 at 274.7 uL/sec +Blowing out at A1 of Opentrons Fixed Trash on 12 +Dropping tip into A1 of Opentrons Fixed Trash on 12 + +``` + +This command will blow out 200 total µL of liquid in the trash. If you need to conserve liquid, use [complex liquid handling parameters](index.html#complex-params) to reduce or eliminate the [disposal volume](index.html#param-disposal-volume), or to [blow out](index.html#param-blow-out) in a location other than the trash. + +#### List of Volumes + +Complex commands can aspirate or dispense different amounts for different wells, rather than the same amount across all wells. To do this, set the `volume` parameter to a list of volumes instead of a single number. The list must be the same length as the longer of `source` or `dest`, or the API will raise an error. For example, this command transfers a different amount of liquid into each of wells B1, B2, and B3: + +``` +pipette.transfer( + volume=[20, 40, 60], + source=plate["A1"], + dest=[plate["B1"], plate["B2"], plate["B3"]], +) + +``` + +Setting any item in the list to `0` will skip aspirating and dispensing for the corresponding well. This example takes the command from above and skips B2: + +``` +pipette.transfer( + volume=[20, 0, 60], + source=plate["A1"], + dest=[plate["B1"], plate["B2"], plate["B3"]], +) + +``` + +The pipette dispenses in B1 and B3, and does not move to B2 at all. + +``` +Picking up tip from A1 of tip rack on 3 +Aspirating 20.0 uL from A1 of well plate on 2 at 274.7 uL/sec +Dispensing 20.0 uL into B1 of well plate on 2 at 274.7 uL/sec +Aspirating 60.0 uL from A1 of well plate on 2 at 274.7 uL/sec +Dispensing 60.0 uL into B3 of well plate on 2 at 274.7 uL/sec +Dropping tip into A1 of Opentrons Fixed Trash on 12 + +``` + +This is such a simple example that you might prefer to use two `transfer()` commands instead. Lists of volumes become more useful when they are longer than a couple elements. For example, you can specify `volume` as a list with 96 items and `dest=plate.wells()` to individually control amounts to dispense (and wells to skip) across an entire plate. + +Note + +When the optional `new_tip` parameter is set to `"always"`, the pipette will pick up and drop a tip even for skipped wells. If you don’t want to waste tips, pre\-process your list of sources or destinations and use the result as the argument of your complex command. + +New in version 2\.0: Skip wells for `transfer()` and `distribute()`. + +New in version 2\.8: Skip wells for `consolidate()`. + +### Complex Liquid Handling Parameters + +Complex commands accept a number of optional parameters that give you greater control over the exact steps they perform. + +This page describes the accepted values and behavior of each parameter. The parameters are organized in the order that they first add a step. Some parameters, such as `touch_tip`, add multiple steps. See [Order of Operations](index.html#complex-command-order) for more details on the sequence of steps performed by complex commands. + +The API reference entry for [`InstrumentContext.transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') also lists the parameters and has more information on their implementation as keyword arguments. + +#### Tip Handling + +The `new_tip` parameter controls if and when complex commands pick up new tips from the pipette’s tip racks. It has three possible values: + +| Value | Behavior | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `"once"` | _ Pick up a tip at the start of the command. _ Use the tip for all liquid handling. \* Drop the tip at the end of the command. | +| `"always"` | Pick up and drop a tip for each set of aspirate and dispense steps. | +| `"never"` | Do not pick up or drop tips at all. | + +`"once"` is the default behavior for all complex commands. + +New in version 2\.0\. + +##### Tip Handling Requirements + +`"once"` and `"always"` require that the pipette has an [associated tip rack](index.html#pipette-tip-racks), or the API will raise an error (because it doesn’t know where to pick up a tip from). If the pipette already has a tip attached, the API will also raise an error when it tries to pick up a tip. + +``` +pipette.pick_up_tip() +pipette.transfer( + volume=100, + source=plate["A1"], + dest=[plate["B1"], plate["B2"], plate["B3"]], + new_tip="never", # "once", "always", or None will error +) + +``` + +Conversely, `"never"` requires that the pipette has picked up a tip, or the API will raise an error (because it will attempt to aspirate without a tip attached). + +##### Avoiding Cross\-Contamination + +One reason to set `new_tip="always"` is to avoid cross\-contamination between wells. However, you should always do a dry run of your protocol to test that the pipette is picking up and dropping tips in the way that your application requires. + +[`transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') will pick up a new tip before _every_ aspirate when `new_tip="always"`. This includes when [tip refilling](index.html#complex-tip-refilling) requires multiple aspirations from a single source well. + +[`distribute()`](index.html#opentrons.protocol_api.InstrumentContext.distribute 'opentrons.protocol_api.InstrumentContext.distribute') and [`consolidate()`](index.html#opentrons.protocol_api.InstrumentContext.consolidate 'opentrons.protocol_api.InstrumentContext.consolidate') only pick up one tip, even when `new_tip="always"`. For example, this distribute command returns to the source well a second time, because the amount to be distributed (400 µL total plus disposal volume) exceeds the pipette capacity (300 μL): + +``` +pipette.distribute( + volume=200, + source=plate["A1"], + dest=[plate["B1"], plate["B2"]], + new_tip="always", +) + +``` + +But it _does not_ pick up a new tip after dispensing into B1: + +``` +Picking up tip from A1 of tip rack on 3 +Aspirating 220.0 uL from A1 of well plate on 2 at 92.86 uL/sec +Dispensing 200.0 uL into B1 of well plate on 2 at 92.86 uL/sec +Blowing out at A1 of Opentrons Fixed Trash on 12 +Aspirating 220.0 uL from A1 of well plate on 2 at 92.86 uL/sec +Dispensing 200.0 uL into B2 of well plate on 2 at 92.86 uL/sec +Blowing out at A1 of Opentrons Fixed Trash on 12 +Dropping tip into A1 of Opentrons Fixed Trash on 12 + +``` + +If this poses a contamination risk, you can work around it in a few ways: + +> - Use `transfer()` with `new_tip="always"` instead. +> - Set [`well_bottom_clearance`](index.html#opentrons.protocol_api.InstrumentContext.well_bottom_clearance 'opentrons.protocol_api.InstrumentContext.well_bottom_clearance') high enough that the tip doesn’t contact liquid in the destination well. +> - Use [building block commands](index.html#v2-atomic-commands) instead of complex commands. + +#### Mix Before + +The `mix_before` parameter controls mixing in source wells before each aspiration. Its value must be a [`tuple`](https://docs.python.org/3/library/stdtypes.html#tuple '(in Python v3.12)') with two numeric values. The first value is the number of repetitions, and the second value is the amount of liquid to mix in µL. + +For example, this transfer command will mix 50 µL of liquid 3 times before each of its aspirations: + +``` +pipette.transfer( + volume=100, + source=plate["A1"], + dest=[plate["B1"], plate["B2"]], + mix_before=(3, 50), +) + +``` + +New in version 2\.0\. + +Mixing occurs before every aspiration, including when [tip refilling](index.html#complex-tip-refilling) is required. + +Note + +[`consolidate()`](index.html#opentrons.protocol_api.InstrumentContext.consolidate 'opentrons.protocol_api.InstrumentContext.consolidate') ignores any value of `mix_before`. Mixing on the second and subsequent aspirations of a consolidate command would defeat its purpose: to aspirate multiple times in a row, from different wells, _before_ dispensing. + +#### Disposal Volume + +The `disposal_volume` parameter controls how much extra liquid is aspirated as part of a [`distribute()`](index.html#opentrons.protocol_api.InstrumentContext.distribute 'opentrons.protocol_api.InstrumentContext.distribute') command. Including a disposal volume can improve the accuracy of each dispense. The pipette blows out the disposal volume of liquid after dispensing. To skip aspirating and blowing out extra liquid, set `disposal_volume=0`. + +By default, `disposal_volume` is the [minimum volume](index.html#new-pipette-models) of the pipette, but you can set it to any amount: + +``` +pipette.distribute( + volume=100, + source=plate["A1"], + dest=[plate["B1"], plate["B2"]], + disposal_volume=10, # reduce from default 20 µL to 10 µL +) + +``` + +New in version 2\.0\. + +If the amount to aspirate plus the disposal volume exceeds the tip’s capacity, `distribute()` will use a [tip refilling strategy](index.html#complex-tip-refilling). In such cases, the pipette will aspirate and blow out the disposal volume _for each aspiration_. For example, this command will require tip refilling with a 1000 µL pipette: + +``` +pipette.distribute( + volume=120, + source=reservoir["A1"], + dest=[plate.columns()[0]], + disposal_volume=50, +) + +``` + +The amount to dispense in the destination is 960 µL (120 µL for each of 8 wells in the column). Adding the 50 µL disposal volume exceeds the 1000 µL capacity of the tip. The command will be split across two aspirations, each with the full disposal volume of 50 µL. The pipette will dispose _a total of 100 µL_ during the command. + +Note + +[`transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') will not aspirate additional liquid if you set `disposal_volume`. However, it will perform a very small blow out after each dispense. + +[`consolidate()`](index.html#opentrons.protocol_api.InstrumentContext.consolidate 'opentrons.protocol_api.InstrumentContext.consolidate') ignores `disposal_volume` completely. + +#### Touch Tip + +The `touch_tip` parameter accepts a Boolean value. When `True`, a touch tip step occurs after every aspirate and dispense. + +For example, this transfer command aspirates, touches the tip at the source, dispenses, and touches the tip at the destination: + +``` +pipette.transfer( + volume=100, + dest=plate["A1"], + source=plate["B1"], + touch_tip=True, +) + +``` + +New in version 2\.0\. + +Touch tip occurs after every aspiration, including when [tip refilling](index.html#complex-tip-refilling) is required. + +This parameter always uses default motion behavior for touch tip. Use the [touch tip building block command](index.html#touch-tip) if you need to: + +> - Only touch the tip after aspirating or dispensing, but not both. +> - Control the speed, radius, or height of the touch tip motion. + +#### Air Gap + +The `air_gap` parameter controls how much air to aspirate and hold in the bottom of the tip when it contains liquid. The parameter’s value is the amount of air to aspirate in µL. + +Air\-gapping behavior is different for each complex command. The different behaviors all serve the same purpose, which is to never leave the pipette holding liquid at the very bottom of the tip. This helps keep liquids from seeping out of the pipette. + +| Method | Air\-gapping behavior | +| --------------- | ----------------------------------------------------------------------------------------------------------------------- | +| `transfer()` | _ Air gap after each aspiration. _ Pipette is empty after dispensing. | +| `distribute()` | _ Air gap after each aspiration. _ Air gap after dispensing if the pipette isn’t empty. | +| `consolidate()` | _ Air gap after each aspiration. This may create multiple air gaps within the tip. _ Pipette is empty after dispensing. | + +For example, this transfer command will create a 20 µL air gap after each of its aspirations. When dispensing, it will clear the air gap and dispense the full 100 µL of liquid: + +``` +pipette.transfer( + volume=100, + source=plate["A1"], + dest=plate["B1"], + air_gap=20, +) + +``` + +New in version 2\.0\. + +When consolidating, air gaps still occur after every aspiration. In this example, the tip will use 210 µL of its capacity (50 µL of liquid followed by 20 µL of air, repeated three times): + +``` +pipette.consolidate( + volume=50, + source=[plate["A1"], plate["A2"], plate["A3"]], + dest=plate["B1"], + air_gap=20, +) + +``` + +``` +Picking up tip from A1 of tip rack on 3 +Aspirating 50.0 uL from A1 of well plate on 2 at 92.86 uL/sec +Air gap + Aspirating 20.0 uL from A1 of well plate on 2 at 92.86 uL/sec +Aspirating 50.0 uL from A2 of well plate on 2 at 92.86 uL/sec +Air gap + Aspirating 20.0 uL from A2 of well plate on 2 at 92.86 uL/sec +Aspirating 50.0 uL from A3 of well plate on 2 at 92.86 uL/sec +Air gap + Aspirating 20.0 uL from A3 of well plate on 2 at 92.86 uL/sec +Dispensing 210.0 uL into B1 of well plate on 2 at 92.86 uL/sec +Dropping tip into A1 of Opentrons Fixed Trash on 12 + +``` + +If adding an air gap would exceed the pipette’s maximum volume, the complex command will use a [tip refilling strategy](index.html#complex-tip-refilling). For example, this command uses a 300 µL pipette to transfer 300 µL of liquid plus an air gap: + +``` +pipette.transfer( + volume=300, + source=plate["A1"], + dest=plate["B1"], + air_gap=20, +) + +``` + +As a result, the transfer is split into two aspirates of 150 µL, each with their own 20 µL air gap: + +``` +Picking up tip from A1 of tip rack on 3 +Aspirating 150.0 uL from A1 of well plate on 2 at 92.86 uL/sec +Air gap + Aspirating 20.0 uL from A1 of well plate on 2 at 92.86 uL/sec +Dispensing 170.0 uL into B1 of well plate on 2 at 92.86 uL/sec +Aspirating 150.0 uL from A1 of well plate on 2 at 92.86 uL/sec +Air gap + Aspirating 20.0 uL from A1 of well plate on 2 at 92.86 uL/sec +Dispensing 170.0 uL into B1 of well plate on 2 at 92.86 uL/sec +Dropping tip into A1 of Opentrons Fixed Trash on 12 + +``` + +#### Mix After + +The `mix_after` parameter controls mixing in source wells after each dispense. Its value must be a [`tuple`](https://docs.python.org/3/library/stdtypes.html#tuple '(in Python v3.12)') with two numeric values. The first value is the number of repetitions, and the second value is the amount of liquid to mix in µL. + +For example, this transfer command will mix 50 µL of liquid 3 times after each of its dispenses: + +``` +pipette.transfer( + volume=100, + source=plate["A1"], + dest=[plate["B1"], plate["B2"]], + mix_after=(3, 50), +) + +``` + +New in version 2\.0\. + +Note + +[`distribute()`](index.html#opentrons.protocol_api.InstrumentContext.distribute 'opentrons.protocol_api.InstrumentContext.distribute') ignores any value of `mix_after`. Mixing after dispensing would combine (and potentially contaminate) the remaining source liquid with liquid present at the destination. + +#### Blow Out + +There are two parameters that control whether and where the pipette blows out liquid. The `blow_out` parameter accepts a Boolean value. When `True`, the pipette blows out remaining liquid when the tip is empty or only contains the disposal volume. The `blowout_location` parameter controls in which of three locations these blowout actions occur. The default blowout location is the trash. Blowout behavior is different for each complex command. + +| Method | Blowout behavior and location | +| --------------- | --------------------------------------------------------------------------------------------------- | +| `transfer()` | _ Blow out after each dispense. _ Valid locations: `"trash"`, `"source well"`, `"destination well"` | +| `distribute()` | _ Blow out after the final dispense. _ Valid locations: `"trash"`, `"source well"` | +| `consolidate()` | _ Blow out after the only dispense. _ Valid locations: `"trash"`, `"destination well"` | + +For example, this transfer command will blow out liquid in the trash twice, once after each dispense into a destination well: + +``` +pipette.transfer( + volume=100, + source=[plate["A1"], plate["A2"]], + dest=[plate["B1"], plate["B2"]], + blow_out=True, +) + +``` + +New in version 2\.0\. + +Set `blowout_location` when you don’t want to waste any liquid by blowing it out into the trash. For example, you may want to make sure that every last bit of a sample is moved into a destination well. Or you may want to return every last bit of an expensive reagent to the source for use in later pipetting. + +If you need to blow out in a different well, or at a specific location within a well, use the [blow out building block command](index.html#blow-out) instead. + +When setting a blowout location, you _must_ also set `blow_out=True`, or the location will be ignored: + +``` +pipette.transfer( + volume=100, + source=plate["A1"], + dest=plate["B1"], + blow_out=True, # required to set location + blowout_location="destination well", +) + +``` + +New in version 2\.8\. + +With `transfer()`, the pipette will not blow out at all if you only set `blowout_location`. + +`blow_out=True` is also required for distribute commands that blow out by virtue of having a disposal volume: + +``` +pipette.distribute( + volume=100, + source=plate["A1"], + dest=[plate["B1"], plate["B2"]], + disposal_volume=50, # causes blow out + blow_out=True, # still required to set location! + blowout_location="source well", +) + +``` + +With `distribute()`, the pipette will still blow out if you only set `blowout_location`, but in the default location of the trash. + +Note + +If the tip already contains liquid before the complex command, the default blowout location will shift away from the trash. `transfer()` and `distribute()` shift to the source well, and `consolidate()` shifts to the destination well. For example, this transfer command will blow out in well B1 because it’s the source: + +``` +pipette.pick_up_tip() +pipette.aspirate(100, plate["A1"]) +pipette.transfer( + volume=100, + source=plate["B1"], + dest=plate["C1"], + new_tip="never", + blow_out=True, + # no blowout_location +) +pipette.drop_tip() + +``` + +This only occurs when you aspirate and then perform a complex command with `new_tip="never"` and `blow_out=True`. + +#### Trash Tips + +The `trash` parameter controls what the pipette does with tips at the end of complex commands. When `True`, the pipette drops tips into the trash. When `False`, the pipette returns tips to their original locations in their tip rack. + +The default is `True`, so you only have to set `trash` when you want the tip\-returning behavior: + +``` +pipette.transfer( + volume=100, + source=plate["A1"], + dest=plate["B1"], + trash=False, +) + +``` + +New in version 2\.0\. + +Complex liquid handling commands combine multiple [building block commands](index.html#v2-atomic-commands) into a single method call. These commands make it easier to handle larger groups of wells and repeat actions without having to write your own control flow code. They integrate tip\-handling behavior and can pick up, use, and drop multiple tips depending on how you want to handle your liquids. They can optionally perform other actions, like adding air gaps, knocking droplets off the tip, mixing, and blowing out excess liquid from the tip. + +There are three complex liquid handling commands, each optimized for a different liquid handling scenario: + +> - [`InstrumentContext.transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') +> - [`InstrumentContext.distribute()`](index.html#opentrons.protocol_api.InstrumentContext.distribute 'opentrons.protocol_api.InstrumentContext.distribute') +> - [`InstrumentContext.consolidate()`](index.html#opentrons.protocol_api.InstrumentContext.consolidate 'opentrons.protocol_api.InstrumentContext.consolidate') + +Pages in this section of the documentation cover: + +> - [Sources and Destinations](index.html#complex-source-dest): Which wells complex commands aspirate from and dispense to. +> - [Order of Operations](index.html#complex-command-order): The order of basic commands that are part of a complex commmand. +> - [Complex Liquid Handling Parameters](index.html#complex-params): Additional keyword arguments that affect complex command behavior. + +Code samples throughout these pages assume that you’ve loaded the pipettes and labware from the [basic protocol template](index.html#protocol-template). + +## Labware and Deck Positions + +The API automatically determines how the robot needs to move when working with the instruments and labware in your protocol. But sometimes you need direct control over these activities. The API lets you do just that. Specifically, you can control movements relative to labware and deck locations. You can also manage the gantry’s speed and trajectory as it traverses the working area. This document explains how to use API commands to take direct control of the robot and position it exactly where you need it. + +### Position Relative to Labware + +When the robot positions itself relative to a piece of labware, where it moves is determined by the labware definition, the actions you want it to perform, and the labware offsets for a specific deck slot. This section describes how these positional components are calculated and how to change them. + +#### Top, Bottom, and Center + +Every well on every piece of labware has three addressable positions: top, bottom, and center. The position is determined by the labware definition and what the labware is loaded on top of. You can use these positions as\-is or calculate other positions relative to them. + +##### Top + +Let’s look at the [`Well.top()`](index.html#opentrons.protocol_api.Well.top 'opentrons.protocol_api.Well.top') method. It returns a position level with the top of the well, centered in both horizontal directions. + +``` +plate["A1"].top() # the top center of the well + +``` + +This is a good position to use for a [blow out operation](index.html#new-blow-out) or an activity where you don’t want the tip to contact the liquid. In addition, you can adjust the height of this position with the optional argument `z`, which is measured in mm. Positive `z` numbers move the position up, negative `z` numbers move it down. + +``` +plate["A1"].top(z=1) # 1 mm above the top center of the well +plate["A1"].top(z=-1) # 1 mm below the top center of the well + +``` + +New in version 2\.0\. + +##### Bottom + +Let’s look at the [`Well.bottom()`](index.html#opentrons.protocol_api.Well.bottom 'opentrons.protocol_api.Well.bottom') method. It returns a position level with the bottom of the well, centered in both horizontal directions. + +``` +plate["A1"].bottom() # the bottom center of the well + +``` + +This is a good position for [aspirating liquid](index.html#new-aspirate) or an activity where you want the tip to contact the liquid. Similar to the `Well.top()` method, you can adjust the height of this position with the optional argument `z`, which is measured in mm. Positive `z` numbers move the position up, negative `z` numbers move it down. + +``` +plate["A1"].bottom(z=1) # 1 mm above the bottom center of the well +plate["A1"].bottom(z=-1) # 1 mm below the bottom center of the well + # this may be dangerous! + +``` + +Warning + +Negative `z` arguments to `Well.bottom()` will cause the pipette tip to collide with the bottom of the well. Collisions may bend the tip (affecting liquid handling) and the pipette may be higher than expected on the z\-axis until it picks up another tip. + +Flex can detect collisions, and even gentle contact may trigger an overpressure error and cause the protocol to fail. Avoid `z` values less than 1, if possible. + +The OT\-2 has no sensors to detect contact with a well bottom. The protocol will continue even after a collision. + +New in version 2\.0\. + +##### Center + +Let’s look at the [`Well.center()`](index.html#opentrons.protocol_api.Well.center 'opentrons.protocol_api.Well.center') method. It returns a position centered in the well both vertically and horizontally. This can be a good place to start for precise control of positions within the well for unusual or custom labware. + +``` +plate["A1"].center() # the vertical and horizontal center of the well + +``` + +New in version 2\.0\. + +#### Default Positions + +By default, your robot will aspirate and dispense 1 mm above the bottom of wells. This default clearance may not be suitable for some labware geometries, liquids, or protocols. You can change this value by using the [`Well.bottom()`](index.html#opentrons.protocol_api.Well.bottom 'opentrons.protocol_api.Well.bottom') method with the `z` argument, though it can be cumbersome to do so repeatedly. + +If you need to change the aspiration or dispensing height for multiple operations, specify the distance in mm from the well bottom with the [`InstrumentContext.well_bottom_clearance`](index.html#opentrons.protocol_api.InstrumentContext.well_bottom_clearance 'opentrons.protocol_api.InstrumentContext.well_bottom_clearance') object. It has two attributes: `well_bottom_clearance.aspirate` and `well_bottom_clearance.dispense`. These change the aspiration height and dispense height, respectively. + +Modifying these attributes will affect all subsequent aspirate and dispense actions performed by the attached pipette, even those executed as part of a [`transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') operation. This snippet from a sample protocol demonstrates how to work with and change the default clearance: + +``` +# aspirate 1 mm above the bottom of the well (default) +pipette.aspirate(50, plate["A1"]) +# dispense 1 mm above the bottom of the well (default) +pipette.dispense(50, plate["A1"]) + +# change clearance for aspiration to 2 mm +pipette.well_bottom_clearance.aspirate = 2 +# aspirate 2 mm above the bottom of the well +pipette.aspirate(50, plate["A1"]) +# still dispensing 1 mm above the bottom +pipette.dispense(50, plate["A1"]) + +pipette.aspirate(50, plate["A1"]) +# change clearance for dispensing to 10 mm +pipette.well_bottom_clearance.dispense = 10 +# dispense high above the well +pipette.dispense(50, plate["A1"]) + +``` + +New in version 2\.0\. + +### Using Labware Position Check + +All positions relative to labware are adjusted automatically based on labware offset data. Calculate labware offsets by running Labware Position Check during protocol setup, either in the Opentrons App or on the Flex touchscreen. Version 6\.0\.0 and later of the robot software can apply previously calculated offsets on the same robot for the same labware type and deck slot, even across different protocols. + +You should only adjust labware offsets in your Python code if you plan to run your protocol in Jupyter Notebook or from the command line. See [Setting Labware Offsets](index.html#using-lpc) in the Advanced Control article for information. + +### Position Relative to Trash Containers + +Movement to [`TrashBin`](index.html#opentrons.protocol_api.TrashBin 'opentrons.protocol_api.TrashBin') or [`WasteChute`](index.html#opentrons.protocol_api.WasteChute 'opentrons.protocol_api.WasteChute') objects is based on the horizontal _center_ of the pipette. This is different than movement to labware, which is based on the primary channel (the back channel on 8\-channel pipettes, and the back\-left channel on 96\-channel pipettes in default configuration). Using the center of the pipette ensures that all attached tips are over the trash container for blowing out, dropping tips, or other disposal operations. + +Note + +In API version 2\.15 and earlier, trash containers are [`Labware`](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') objects that have a single well. See [`fixed_trash`](index.html#opentrons.protocol_api.ProtocolContext.fixed_trash 'opentrons.protocol_api.ProtocolContext.fixed_trash') and [Position Relative to Labware](#position-relative-labware) above. + +You can adjust the position of the pipette center with the [`TrashBin.top()`](index.html#opentrons.protocol_api.TrashBin.top 'opentrons.protocol_api.TrashBin.top') and [`WasteChute.top()`](index.html#opentrons.protocol_api.WasteChute.top 'opentrons.protocol_api.WasteChute.top') methods. These methods allow adjustments along the x\-, y\-, and z\-axes. In contrast, `Well.top()`, [covered above](#well-top), only allows z\-axis adjustment. With no adjustments, the “top” position is centered on the x\- and y\-axes and is just below the opening of the trash container. + +``` +trash = protocol.load_trash_bin("A3") + +trash # pipette center just below trash top center +trash.top() # same position +trash.top(z=10) # 10 mm higher +trash.top(y=10) # 10 mm towards back, default height + +``` + +New in version 2\.18\. + +Another difference between the trash container `top()` methods and `Well.top()` is that they return an object of the same type, not a [`Location`](index.html#opentrons.types.Location 'opentrons.types.Location'). This helps prevent performing undesired actions in trash containers. For example, you can [`aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') at a location or from a well, but not from a trash container. On the other hand, you can [`blow_out()`](index.html#opentrons.protocol_api.InstrumentContext.blow_out 'opentrons.protocol_api.InstrumentContext.blow_out') at a location, well, trash bin, or waste chute. + +### Position Relative to the Deck + +The robot’s base coordinate system is known as _deck coordinates_. Many API functions use this coordinate system, and you can also reference it directly. It is a right\-handed coordinate system always specified in mm, with the origin `(0, 0, 0)` at the front left of the robot. The positive `x` direction is to the right, the positive `y` direction is to the back, and the positive `z` direction is up. + +You can identify a point in this coordinate system with a [`types.Location`](index.html#opentrons.types.Location 'opentrons.types.Location') object, either as a standard Python [`tuple`](https://docs.python.org/3/library/stdtypes.html#tuple '(in Python v3.12)') of three floats, or as an instance of the [`namedtuple`](https://docs.python.org/3/library/collections.html#collections.namedtuple '(in Python v3.12)') [`types.Point`](index.html#opentrons.types.Point 'opentrons.types.Point'). + +Note + +There are technically multiple vertical axes. For example, `z` is the axis of the left pipette mount and `a` is the axis of the right pipette mount. There are also pipette plunger axes: `b` (left) and `c` (right). You usually don’t have to refer to these axes directly, since most motion commands are issued to a particular pipette and the robot automatically selects the correct axis to move. Similarly, [`types.Location`](index.html#opentrons.types.Location 'opentrons.types.Location') only deals with `x`, `y`, and `z` values. + +### Independent Movement + +For convenience, many methods have location arguments and incorporate movement automatically. This section will focus on moving the pipette independently, without performing other actions like `aspirate()` or `dispense()`. + +#### Move To + +The [`InstrumentContext.move_to()`](index.html#opentrons.protocol_api.InstrumentContext.move_to 'opentrons.protocol_api.InstrumentContext.move_to') method moves a pipette to any reachable location on the deck. If the pipette has picked up a tip, it will move the end of the tip to that position; if it hasn’t, it will move the pipette nozzle to that position. + +The [`move_to()`](index.html#opentrons.protocol_api.InstrumentContext.move_to 'opentrons.protocol_api.InstrumentContext.move_to') method requires the [`Location`](index.html#opentrons.types.Location 'opentrons.types.Location') argument. The location can be automatically generated by methods like `Well.top()` and `Well.bottom()` or one you’ve created yourself, but you can’t move a pipette to a well directly: + +``` +pipette.move_to(plate["A1"]) # error; can't move to a well itself +pipette.move_to(plate["A1"].bottom()) # move to the bottom of well A1 +pipette.move_to(plate["A1"].top()) # move to the top of well A1 +pipette.move_to(plate["A1"].bottom(z=2)) # move to 2 mm above the bottom of well A1 +pipette.move_to(plate["A1"].top(z=-2)) # move to 2 mm below the top of well A1 + +``` + +When using `move_to()`, by default the pipette will move in an arc: first upwards, then laterally to a position above the target location, and finally downwards to the target location. If you have a reason for doing so, you can force the pipette to move in a straight line to the target location: + +``` +pipette.move_to(plate["A1"].top(), force_direct=True) + +``` + +Warning + +Moving without an arc runs the risk of the pipette colliding with objects on the deck. Be very careful when using this option, especially when moving longer distances. + +Small, direct movements can be useful for working inside of a well, without having the tip exit and re\-enter the well. This code sample demonstrates how to move the pipette to a well, make direct movements inside that well, and then move on to a different well: + +``` +pipette.move_to(plate["A1"].top()) +pipette.move_to(plate["A1"].bottom(1), force_direct=True) +pipette.move_to(plate["A1"].top(-2), force_direct=True) +pipette.move_to(plate["A2"].top()) + +``` + +New in version 2\.0\. + +#### Points and Locations + +When instructing the robot to move, it’s important to consider the difference between the [`Point`](index.html#opentrons.types.Point 'opentrons.types.Point') and [`Location`](index.html#opentrons.types.Location 'opentrons.types.Location') types. + +- Points are ordered tuples or named tuples: `Point(10, 20, 30)`, `Point(x=10, y=20, z=30)`, and `Point(z=30, y=20, x=10)` are all equivalent. +- Locations are a higher\-order tuple that combines a point with a reference object: a well, a piece of labware, or `None` (the deck). + +This distinction is important for the [`Location.move()`](index.html#opentrons.types.Location.move 'opentrons.types.Location.move') method, which operates on a location, takes a point as an argument, and outputs an updated location. To use this method, include `from opentrons import types` at the start of your protocol. The `move()` method does not mutate the location it is called on, so to perform an action at the updated location, use it as an argument of another method or save it to a variable. For example: + +``` +# get the location at the center of well A1 +center_location = plate["A1"].center() + +# get a location 1 mm right, 1 mm back, and 1 mm up from the center of well A1 +adjusted_location = center_location.move(types.Point(x=1, y=1, z=1)) + +# aspirate 1 mm right, 1 mm back, and 1 mm up from the center of well A1 +pipette.aspirate(50, adjusted_location) + +# dispense at the same location +pipette.dispense(50, center_location.move(types.Point(x=1, y=1, z=1))) + +``` + +Note + +The additional `z` arguments of the `top()` and `bottom()` methods (see [Position Relative to Labware](#position-relative-labware) above) are shorthand for adjusting the top and bottom locations with `move()`. You still need to use `move()` to adjust these positions along the x\- or y\-axis: + +``` +# the following are equivalent +pipette.move_to(plate["A1"].bottom(z=2)) +pipette.move_to(plate["A1"].bottom().move(types.Point(z=2))) + +# adjust along the y-axis +pipette.move_to(plate["A1"].bottom().move(types.Point(y=2))) + +``` + +New in version 2\.0\. + +### Movement Speeds + +In addition to instructing the robot where to move a pipette, you can also control the speed at which it moves. Speed controls can be applied either to all pipette motions or to movement along a particular axis. + +Note + +Like all mechanical systems, Opentrons robots have resonant frequencies that depend on their construction and current configuration. It’s possible to set a speed that causes your robot to resonate, producing louder sounds than typical operation. This is safe, but if you find it annoying, increase or decrease the speed slightly. + +#### Gantry Speed + +The robot’s gantry usually moves as fast as it can given its construction. The default speed for Flex varies between 300 and 350 mm/s. The OT\-2 default is 400 mm/s. However, some experiments or liquids may require slower movements. In this case, you can reduce the gantry speed for a specific pipette by setting [`InstrumentContext.default_speed`](index.html#opentrons.protocol_api.InstrumentContext.default_speed 'opentrons.protocol_api.InstrumentContext.default_speed') like this: + +``` +pipette.move_to(plate["A1"].top()) # move to the first well at default speed +pipette.default_speed = 100 # reduce pipette speed +pipette.move_to(plate["D6"].top()) # move to the last well at the slower speed + +``` + +Warning + +These default speeds were chosen because they’re the maximum speeds that Opentrons knows will work with the gantry. Your robot may be able to move faster, but you shouldn’t increase this value unless instructed by Opentrons Support. + +New in version 2\.0\. + +#### Axis Speed Limits + +In addition to controlling the overall gantry speed, you can set speed limits for each of the individual axes: `x` (gantry left/right motion), `y` (gantry forward/back motion), `z` (left pipette up/down motion), and `a` (right pipette up/down motion). Unlike `default_speed`, which is a pipette property, axis speed limits are stored in a protocol property [`ProtocolContext.max_speeds`](index.html#opentrons.protocol_api.ProtocolContext.max_speeds 'opentrons.protocol_api.ProtocolContext.max_speeds'); therefore the `x` and `y` values affect all movements by both pipettes. This property works like a dictionary, where the keys are axes, assigning a value to a key sets a max speed, and deleting a key or setting it to `None` resets that axis’s limit to the default: + +``` + protocol.max_speeds["x"] = 50 # limit x-axis to 50 mm/s + del protocol.max_speeds["x"] # reset x-axis limit + protocol.max_speeds["a"] = 10 # limit a-axis to 10 mm/s + protocol.max_speeds["a"] = None # reset a-axis limit + +``` + +Note that `max_speeds` can’t set limits for the pipette plunger axes (`b` and `c`); instead, set the flow rates or plunger speeds as described in [Pipette Flow Rates](index.html#new-plunger-flow-rates). + +New in version 2\.0\. + +## Runtime Parameters + +### Choosing Good Parameters + +The first decision you need to make when adding parameters to your protocol is “What should be parameterized?” Your goals in adding parameters should be the following: + +1. **Add flexibility.** Accommodate changes from run to run or from lab to lab. +2. **Work efficiently.** Don’t burden run setup with too many choices or confusing options. +3. **Avoid errors.** Ensure that every combination of parameters produces an analyzable, runnable protocol. + +The trick to choosing good parameters is reasoning through the choices the protocol’s users may make. If any of them lead to nonsensical outcomes or errors, adjust the parameters — or how your protocol [uses parameter values](index.html#using-rtp) — to avoid those situations. + +#### Build on a Task + +Consider what scientific task is at the heart of your protocol, and build parameters that contribute to, rather than diverge from it. + +For example, it makes sense to add a parameter for number of samples to a DNA prep protocol that uses a particular reagent kit. But it wouldn’t make sense to add a parameter for _which reagent kit_ to use for DNA prep. That kind of parameter would affect so many aspects of the protocol that it would make more sense to maintain a separate protocol for each kit. + +Also consider how a small number of parameters can combine to produce many useful outputs. Take the serial dilution task from the [Tutorial](index.html#tutorial) as an example. We could add just three parameters to it: number of dilutions, dilution factor, and number of rows. Now that single protocol can produce a whole plate that gradually dilutes, a 2×4 grid that rapidly dilutes, and _thousands_ of other combinations. + +#### Consider Contradictions + +Here’s a common time\-saving use of parameters: your protocol requires a 1\-channel pipette and an 8\-channel pipette, but it doesn’t matter which mount they’re attached to. Without parameters, you would have to assign the mounts in your protocol. Then if the robot is set up in the reverse configuration, you’d have to either physically swap the pipettes or modify your protocol. + +One way to get this information is to ask which mount the 1\-channel pipette is on, and which mount the 8\-channel pipette is on. But if a technician answers “left” to both questions — even by accident — the API will raise an error, because you can’t load two pipettes on a single mount. It’s no better to flip things around by asking which pipette is on the left mount, and which pipette is on the right mount. Now the technician can say that both mounts have a 1\-channel pipette. This is even more dangerous, because it _might not_ raise any errors in analysis. The protocol could run “successfully” on a robot with two 1\-channel pipettes, but produce completely unintended results. + +The best way to avoid these contradictions is to collapse the two questions into one, with limited choices. Where are the pipettes mounted? Either the 1\-channel is on the left and the 8\-channel on the right, or the 8\-channel is on the left and the 1\-channel is on the right. This approach is best for several reasons: + +- It avoids analysis errors. +- It avoids potentially dangerous execution errors. +- It only requires answering one question instead of two. +- The [phrasing of the question and answer](index.html#rtp-style) makes it clear that the protocol requires exactly one of each pipette type. + +#### Set Boundaries + +Numerical parameters support minimum and maximum values, which you should set to avoid incorrect inputs that are outside of your protocol’s possibile actions. + +Consider our earlier example of parameterizing serial dilution. Each of the three numerical parameters have logical upper and lower bounds, which we need to enforce to get sensible results. + +- _Number of dilutions_ must be between 0 and 11 on a 96\-well plate. And it may make sense to require at least 1 dilution. +- _Dilution factor_ is a ratio, which we can express as a decimal number that must be between 0 and 1\. +- _Number of rows_ must be between 1 and 8 on a 96\-well plate. + +What if you wanted to perform a dilution with 20 repetitions? It’s possible with two 96\-well plates, or with a 384\-well plate. You could set the maximum for the number of dilutions to 24 and allow for these possibilities — either switching the plate type or loading an additional plate based on the provided value. + +But what if the technician wanted to do just 8 repetitions on a 384\-well plate? That would require an additional parameter, an additional choice by the technician, and additional logic in your protocol code. It’s up to you as the protocol author to decide if adding more parameters will make protocol setup overly difficult. Sometimes it’s more efficient to work with two or three simple protocols rather than one that’s long and complex. + +### Defining Parameters + +To use parameters, you need to define them in [a separate function](#add-parameters) within your protocol. Each parameter definition has two main purposes: to specify acceptable values, and to inform the protocol user what the parameter does. + +Depending on the [type of parameter](#rtp-types), you’ll need to specify some or all of the following. + +| Attribute | Details | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `variable_name` | _ A unique name for [referencing the parameter value](index.html#using-rtp) elsewhere in the protocol. _ Must meet the usual requirements for [naming objects in Python](https://docs.python.org/3/reference/lexical_analysis.html#identifiers). | +| `display_name` | _ A label for the parameter shown in the Opentrons App or on the touchscreen. _ Maximum 30 characters. | +| `description` | _ An optional longer explanation of what the parameter does, or how its values will affect the execution of the protocol. _ Maximum 100 characters. | +| `default` | \* The value the parameter will have if the technician makes no changes to it during run setup. | +| `minimum` and `maximum` | _ For numeric parameters only. _ Allows free entry of any value within the range (inclusive). _ Both values are required. _ Can’t be used at the same time as `choices`. | +| `choices` | _ For numeric or string parameters. _ Provides a fixed list of values to choose from. _ Each choice has its own display name and value. _ Can’t be used at the same time as `minimum` and `maximum`. | +| `units` | _ Optional, for numeric parameters with `minimum` and `maximum` only. _ Displays after the number during run setup. _ Does not affect the parameter’s value or protocol execution. _ Maximum 10 characters. | + +#### The `add_parameters()` Function + +All parameter definitions are contained in a Python function, which must be named `add_parameters` and takes a single argument. Define `add_parameters()` before the `run()` function that contains protocol commands. + +The examples on this page assume the following definition, which uses the argument name `parameters`. The type specification of the argument is optional. + +``` +def add_parameters(parameters: protocol_api.Parameters): + +``` + +Within this function definition, call methods on `parameters` to define parameters. The next section demonstrates how each type of parameter has its own method. + +#### Types of Parameters + +The API supports four types of parameters: Boolean ([`bool`](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')), integer ([`int`](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)')), floating point number ([`float`](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')), and string ([`str`](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')). It is not possible to mix types within a single parameter. + +##### Boolean Parameters + +Boolean parameters are `True` or `False` only. + +``` +parameters.add_bool( + variable_name="dry_run", + display_name="Dry Run", + description="Skip incubation delays and shorten mix steps.", + default=False +) + +``` + +During run setup, the technician can toggle between the two values. In the Opentrons App, Boolean parameters appear as a toggle switch. On the touchscreen, they appear as _On_ or _Off_, for `True` and `False` respectively. + +New in version 2\.18\. + +##### Integer Parameters + +Integer parameters either accept a range of numbers or a list of numbers. You must specify one or the other; you can’t create an open\-ended prompt that accepts any integer. + +To specify a range, include `minimum` and `maximum`. + +``` +parameters.add_int( + variable_name="volume", + display_name="Aspirate volume", + description="How much to aspirate from each sample.", + default=20, + minimum=10, + maximum=100, + unit="µL" +) + +``` + +During run setup, the technician can enter any integer value from the minimum up to the maximum. Entering a value outside of the range will show an error. At that point, they can correct their custom value or restore the default value. + +To specify a list of numbers, include `choices`. Each choice is a dictionary with entries for display name and value. The display names let you briefly explain the effect each choice will have. + +``` +parameters.add_int( + variable_name="volume", + display_name="Aspirate volume", + description="How much to aspirate from each sample.", + default=20, + choices=[ + {"display_name": "Low (10 µL)", "value": 10}, + {"display_name": "Medium (20 µL)", "value": 20}, + {"display_name": "High (50 µL)", "value": 50}, + ] +) + +``` + +During run setup, the technician can choose from a menu of the provided choices. + +New in version 2\.18\. + +##### Float Parameters + +Float parameters either accept a range of numbers or a list of numbers. You must specify one or the other; you can’t create an open\-ended prompt that accepts any floating point number. + +Specifying a range or list is done exactly the same as in the integer examples above. The only difference is that all values must be floating point numbers. + +``` +parameters.add_float( + variable_name="volume", + display_name="Aspirate volume", + description="How much to aspirate from each sample.", + default=5.0, + choices=[ + {"display_name": "Low (2.5 µL)", "value": 2.5}, + {"display_name": "Medium (5 µL)", "value": 5.0}, + {"display_name": "High (10 µL)", "value": 10.0}, + ] +) + +``` + +New in version 2\.18\. + +##### String Parameters + +String parameters only accept a list of values. You can’t currently prompt for free text entry of a string value. + +To specify a list of strings, include `choices`. Each choice is a dictionary with entries for display name and value. Only the display name will appear during run setup. + +A common use for string display names is to provide an easy\-to\-read version of an API load name. You can also use them to briefly explain the effect each choice will have. + +``` +parameters.add_str( + variable_name="pipette", + display_name="Pipette type", + choices=[ + {"display_name": "1-Channel 50 µL", "value": "flex_1channel_50"}, + {"display_name": "8-Channel 50 µL", "value": "flex_8channel_50"}, + ], + default="flex_1channel_50", +) + +``` + +During run setup, the technician can choose from a menu of the provided choices. + +New in version 2\.18\. + +### Using Parameters + +Once you’ve [defined parameters](index.html#defining-rtp), their values are accessible anywhere within the `run()` function of your protocol. + +#### The `params` Object + +Protocols with parameters have a [`ProtocolContext.params`](index.html#opentrons.protocol_api.ProtocolContext.params 'opentrons.protocol_api.ProtocolContext.params') object, which contains the values of all parameters as set during run setup. Each attribute of `params` corresponds to the `variable_name` of a parameter. + +For example, consider a protocol that defines the following three parameters: + +- `add_bool` with `variable_name="dry_run"` +- `add_int` with `variable_name="sample_count"` +- `add_float` with `variable_name="volume"` + +Then `params` will gain three attributes: `params.dry_run`, `params.sample_count`, and `params.volume`. You can use these attributes anywhere you want to access their values, including directly as arguments of methods. + +``` +if protocol.params.dry_run is False: + pipette.mix(repetitions=10, volume=protocol.params.volume) + +``` + +You can also save parameter values to variables with names of your choosing. + +#### Parameter Types + +Each attribute of `params` has the type corresponding to its parameter definition. Keep in mind the parameter’s type when using its value in different contexts. + +Say you wanted to add a comment to the run log, stating how many samples the protocol will process. Since `sample_count` is an `int`, you’ll need to cast it to a `str` or the API will raise an error. + +``` +protocol.comment( + "Processing " + str(protocol.params.sample_count) + " samples." +) + +``` + +Also be careful with `int` types when performing calculations: dividing an `int` by an `int` with the `/` operator always produces a `float`, even if there is no remainder. The [sample count use case](index.html#use-case-sample-count) converts a sample count to a column count by dividing by 8 — but it uses the `//` integer division operator, so the result can be used for creating ranges, slicing lists, and as `int` argument values without having to cast it in those contexts. + +#### Limitations + +Since `params` is only available within the `run()` function, there are certain aspects of a protocol that parameter values can’t affect. These include, but are not limited to the following: + +| Information | Location | +| -------------------------------- | ----------------------------------------------- | +| `import` statements | At the beginning of the protocol. | +| Robot type (Flex or OT\-2\) | In the `requirements` dictionary. | +| API version | In the `requirements` or `metadata` dictionary. | +| Protocol name | In the `metadata` dictionary. | +| Protocol description | In the `metadata` dictionary. | +| Protocol author | In the `metadata` dictionary. | +| Other runtime parameters | In the `add_parameters()` function. | +| Non\-nested function definitions | Anywhere outside of `run()`. | + +Additionally, keep in mind that updated parameter values are applied by reanalyzing the protocol. This means you can’t depend on updated values for any action that takes place _prior to reanalysis_. + +An example of such an action is applying labware offset data. Say you have a parameter that changes the type of well plate you load in a particular slot: + +``` +# within add_parameters() +parameters.add_str( + variable_name="plate_type", + display_name="Well plate type", + choices=[ + {"display_name": "Corning", "value": "corning_96_wellplate_360ul_flat"}, + {"display_name": "NEST", "value": "nest_96_wellplate_200ul_flat"}, + ], + default="corning_96_wellplate_360ul_flat", +) + +# within run() +plate = protocol.load_labware( + load_name=protocol.params.plate_type, location="D2" +) + +``` + +When performing run setup, you’re prompted to apply offsets before selecting parameter values. This is your only opportunity to apply offsets, so they’re applied for the default parameter values — in this case, the Corning plate. If you then change the “Well plate type” parameter to the NEST plate, the NEST plate will have default offset values (0\.0 on all axes). You can fix this by running Labware Position Check, since it takes place after reanalysis, or by using [`Labware.set_offset()`](index.html#opentrons.protocol_api.Labware.set_offset 'opentrons.protocol_api.Labware.set_offset') in your protocol. + +### Parameter Use Case – Sample Count + +Choosing how many samples to process is important for efficient automation. This use case explores how a single parameter for sample count can have pervasive effects throughout a protocol. The examples are adapted from an actual parameterized protocol for DNA prep. The sample code will use 8\-channel pipettes to process 8, 16, 24, or 32 samples. + +At first glance, it might seem like sample count would primarily affect liquid transfers to and from sample wells. But when using the Python API’s full range of capabilities, it affects: + +- How many tip racks to load. +- The initial volume and placement of reagents. +- Pipetting to and from samples. +- If and when tip racks need to be replaced. + +To keep things as simple as possible, this use case only focuses on setting up and using the value of the sample count parameter, which is just one of several parameters present in the full protocol. + +#### From Samples to Columns + +First of all, we need to set up the sample count parameter so it’s both easy for technicians to understand during protocol setup and easy for us to use in the protocol’s `run()` function. + +We want to limit the number of samples to 8, 16, 24, or 32, so we’ll use an integer parameter with choices: + +``` +def add_parameters(parameters): + + parameters.add_int( + variable_name="sample_count", + display_name="Sample count", + description="Number of input DNA samples.", + default=24, + choices=[ + {"display_name": "8", "value": 8}, + {"display_name": "16", "value": 16}, + {"display_name": "24", "value": 24}, + {"display_name": "32", "value": 32}, + ] + ) + +``` + +All of the possible values are multiples of 8, because the protocol will use an 8\-channel pipette to process an entire column of samples at once. Considering how 8\-channel pipettes access wells, it may be more useful to operate with a _column count_ in code. We can set a `column_count` very early in the `run()` function by accessing the value of `params.sample_count` and dividing it by 8: + +``` +def run(protocol): + + column_count = protocol.params.sample_count // 8 + +``` + +Most examples below will use `column_count`, rather than redoing (and retyping!) this calculation multiple times. + +#### Loading Tip Racks + +Tip racks come first in most protocols. To ensure that the protocol runs to completion, we need to load enough tip racks to avoid running out of tips. + +We could load as many tip racks as are needed for our maximum number of samples, but that would be suboptimal. Run setup is faster when the technician doesn’t have to load extra items onto the deck. So it’s best to examine the protocol’s steps and determine how many racks are needed for each value of `sample_count`. + +In the case of this DNA prep protocol, we can create formulas for the number of 200 µL and 50 µL tip racks needed. The following factors go into these computations: + +- 50 µL tips + - 1 fixed action that picks up once per protocol. + - 7 variable actions that pick up once per sample column. +- 200 µL tips + - 2 fixed actions that pick up once per protocol. + - 11 variable actions that pick up once per sample column. + +Since each tip rack has 12 columns, divide the number of pickup actions by 12 to get the number of racks needed. And we always need to round up — performing 13 pickups requires 2 racks. The [`math.ceil()`](https://docs.python.org/3/library/math.html#math.ceil '(in Python v3.12)') method rounds up to the nearest integer. We’ll add `from math import ceil` at the top of the protocol and then calculate the number of tip racks as follows: + +``` +tip_rack_50_count = ceil((1 + 7 * column_count) / 12) +tip_rack_200_count = ceil((2 + 13 * column_count) / 12) + +``` + +Running the numbers shows that the maximum combined number of tip racks is 7\. Now we have to decide where to load up to 7 racks, working around the modules and other labware on the deck. Assuming we’re running this protocol on a Flex with staging area slots, they’ll all fit! (If you don’t have staging area slots, you can load labware off\-deck instead.) We’ll reserve these slots for the different size racks: + +``` +tip_rack_50_slots = ["B3", "C3", "B4"] +tip_rack_200_slots = ["A2", "B2", "A3", "A4"] + +``` + +Finally, we can combine this information to call [`load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware'). Depending on the number of racks needed, we’ll slice that number of elements from the slot list and use a [list comprehension](https://docs.python.org/2/tutorial/datastructures.html#list-comprehensions) to gather up the loaded tip racks. For the 50 µL tips, this would look like: + +``` +tip_racks_50 = [ + protocol.load_labware( + load_name="opentrons_flex_96_tiprack_50ul", + location=slot + ) + for slot in tip_rack_50_slots[:tip_rack_50_count] +] + +``` + +Then we can associate those lists of tip racks directly with each pipette as we load them. All together, the start of our `run()` function looks like this: + +``` +# calculate column count from sample count +column_count = protocol.params.sample_count // 8 + +# calculate number of required tip racks +tip_rack_50_count = ceil((1 + 7 * column_count) / 12) +tip_rack_200_count = ceil((2 + 13 * column_count) / 12) + +# assign tip rack locations (maximal case) +tip_rack_50_slots = ["B3", "C3", "B4"] +tip_rack_200_slots = ["A2", "B2", "A3", "A4"] + +# create lists of loaded tip racks +# limit to number of needed racks for each type +tip_racks_50 = [ + protocol.load_labware( + load_name="opentrons_flex_96_tiprack_50ul", + location=slot + ) + for slot in tip_rack_50_slots[:tip_rack_50_count] +] +tip_racks_200 = [ + protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + location=slot + ) + for slot in tip_rack_200_slots[:tip_rack_200_count] +] + +pipette_50 = protocol.load_instrument( + instrument_name="flex_8channel_50", + mount="right", + tip_racks=tip_racks_50 +) +pipette_1000 = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=tip_racks_200 +) + +``` + +This code will load as few as 3 tip racks and as many as 7, and associate them with the correct pipettes — all based on a single choice from a dropdown menu at run setup. + +#### Loading Liquids + +Next come the reagents, samples, and the labware that holds them. + +The required volume of each reagent is dependent on the sample count. While the full protocol defines more than ten liquids, we’ll show three reagents plus the samples here. + +First, let’s load a reservoir and [define](index.html#defining-liquids) the three example liquids. Definitions only specify the name, description, and display color, so our sample count parameter doesn’t come into play yet: + +``` +# labware to hold reagents +reservoir = protocol.load_labware( + load_name="nest_12_reservoir_15ml", location="C2" +) + +# reagent liquid definitions +ampure_liquid = protocol.define_liquid( + name="AMPure", description="AMPure Beads", display_color="#704848" +) +tagstop_liquid = protocol.define_liquid( + name="TAGSTOP", description="Tagmentation Stop", display_color="#FF0000" +) +twb_liquid = protocol.define_liquid( + name="TWB", description="Tagmentation Wash Buffer", display_color="#FFA000" +) + +``` + +Now we’ll bring sample count into consideration as we [load the liquids](index.html#loading-liquids). The application requires the following volumes for each column of samples: + +| Liquid | Volume (µL per column) | +| ------------------------ | ---------------------- | +| AMPure Beads | 180 | +| Tagmentation Stop | 10 | +| Tagmentation Wash Buffer | 900 | + +To calculate the total volume for each liquid, we’ll multiply these numbers by `column_count` and by 1\.1 (to ensure that the pipette can aspirate the required volume without drawing in air at the bottom of the well). This calculation can be done inline as the `volume` value of [`load_liquid()`](index.html#opentrons.protocol_api.Well.load_liquid 'opentrons.protocol_api.Well.load_liquid'): + +``` +reservoir["A1"].load_liquid( + liquid=ampure_liquid, volume=180 * column_count * 1.1 +) +reservoir["A2"].load_liquid( + liquid=tagstop_liquid, volume=10 * column_count * 1.1 +) +reservoir["A4"].load_liquid( + liquid=twb_liquid, volume=900 * column_count * 1.1 +) + +``` + +Now, for example, the volume of AMPure beads to load will vary from 198 µL for a single sample column up to 792 µL for four columns. + +Tip + +Does telling a technician to load 792 µL of a liquid seem overly precise? Remember that you can perform any calculation you like to set the value of `volume`! For example, you could round the AMPure volume up to the nearest 10 µL: + +``` +volume=ceil((180 * column_count * 1.1) / 10) * 10 + +``` + +Finally, it’s good practice to label the wells where the samples reside. The sample plate starts out atop the Heater\-Shaker Module: + +``` +hs_mod = protocol.load_module( + module_name="heaterShakerModuleV1", location="D1" +) +hs_adapter = hs_mod.load_adapter(name="opentrons_96_pcr_adapter") +sample_plate = hs_adapter.load_labware( + name="opentrons_96_wellplate_200ul_pcr_full_skirt", + label="Sample Plate", +) + +``` + +Now we can construct a `for` loop to label each sample well with `load_liquid()`. The simplest way to do this is to combine our original _sample count_ with the fact that the [`Labware.wells()`](index.html#opentrons.protocol_api.Labware.wells 'opentrons.protocol_api.Labware.wells') accessor returns wells top\-to\-bottom, left\-to\-right: + +``` +# define sample liquid +sample_liquid = protocol.define_liquid( + name="Samples", description=None, display_color="#52AAFF" +) + +# load 40 µL in each sample well +for w in range(protocol.params.sample_count): + sample_plate.wells()[w].load_liquid(liquid=sample_liquid, volume=40) + +``` + +#### Processing Samples + +When it comes time to process the samples, we’ll return to working by column, since the protocol uses an 8\-channel pipette. There are many pipetting stages in the full protocol, but this section will examine just the stage for adding the Tagmentation Stop liquid. The same techniques would apply to similar stages. + +For pipetting in the original sample locations, we’ll command the 50 µL pipette to move to some or all of A1–A4 on the sample plate. Similar to when we loaded tip racks earlier, we can use `column_count` to slice a list containing these well names, and then iterate over that list with a `for` loop: + +``` +for w in ["A1", "A2", "A3", "A4"][:column_count]: + pipette_50.pick_up_tip() + pipette_50.aspirate(volume=13, location=reservoir["A2"].bottom()) + pipette_50.dispense(volume=3, location=reservoir["A2"].bottom()) + pipette_50.dispense(volume=10, location=sample_plate[w].bottom()) + pipette_50.move_to(location=sample_plate[w].bottom()) + pipette_50.mix(repetitions=10, volume=20) + pipette_50.blow_out(location=sample_plate[w].top(z=-2)) + pipette_50.drop_tip() + +``` + +Each time through the loop, the pipette will fill from the same well of the reservoir and then dispense (and mix and blow out) in a different column of the sample plate. + +Later steps of the protocol will move intermediate samples to the middle of the plate (columns 5–8\) and final samples to the right side of the plate (columns 9–12\). When moving directly from one set of columns to another, we have to track _both lists_ with the `for` loop. The [`zip()`](https://docs.python.org/3/library/functions.html#zip '(in Python v3.12)') function lets us pair up the lists of well names and step through them in parallel: + +``` +for initial, intermediate in zip( + ["A1", "A2", "A3", "A4"][:column_count], + ["A5", "A6", "A7", "A8"][:column_count], +): + pipette_50.pick_up_tip() + pipette_50.aspirate(volume=13, location=sample_plate[initial]) + pipette_50.dispense(volume=13, location=sample_plate[intermediate]) + pipette_50.drop_tip() + +``` + +This will transfer from column 1 to 5, 2 to 6, and so on — depending on the number of samples chosen during run setup. + +#### Replenishing Tips + +For the higher values of `protocol.params.sample_count`, the protocol will load tip racks in the staging area slots (column 4\). Since pipettes can’t reach these slots, we need to move these tip racks into the working area (columns 1–3\) before issuing a pipetting command that targets them, or the API will raise an error. + +A protocol without parameters will always run out of tips at the same time — just add a [`move_labware()`](index.html#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') command when that happens. But as we saw in the Processing Samples section above, our parameterized protocol will go through tips at a different rate depending on the sample count. + +In our simplified example, we know that when the sample count is 32, the first 200 µL tip rack will be exhausted after three stages of pipetting using the 1000 µL pipette. So, after that step, we could add: + +``` +if protocol.params.sample_count == 32: + protocol.move_labware( + labware=tip_racks_200[0], + new_location=chute, + use_gripper=True, + ) + protocol.move_labware( + labware=tip_racks_200[-1], + new_location="A2", + use_gripper=True, + ) + +``` + +This will replace the first 200 µL tip rack (in slot A2\) with the last 200 µL tip rack (in the staging area). + +However, in the full protocol, sample count is not the only parameter that affects the rate of tip use. It would be unwieldy to calculate in advance all the permutations of when tip replenishment is necessary. Instead, before each stage of the protocol, we could use [`Well.has_tip()`](index.html#opentrons.protocol_api.Well.has_tip 'opentrons.protocol_api.Well.has_tip') to check whether the first tip rack is empty. If the _last well_ of the rack is empty, we can assume that the entire rack is empty and needs to be replaced: + +``` +if tip_racks_200[0].wells()[-1].has_tip is False: + # same move_labware() steps as above + +``` + +For a protocol that uses tips at a faster rate than this one — such that it might exhaust a tip rack in a single `for` loop of pipetting steps — you may have to perform such checks even more frequently. You can even define a function that counts tips or performs `has_tip` checks in combination with picking up a tip, and use that instead of [`pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') every time you pipette. The built\-in capabilities of Python and the methods of the Python Protocol API give you the flexibility to add this kind of smart behavior to your protocols. + +### Parameter Use Case – Dry Run + +When testing out a new protocol, it’s common to perform a dry run to watch your robot go through all the steps without actually handling samples or reagents. This use case explores how to add a single Boolean parameter for whether you’re performing a dry run. + +The code examples will show how this single value can control: + +- Skipping module actions and long delays. +- Reducing mix repetitions to save time. +- Returning tips (that never touched any liquid) to their racks. + +To keep things as simple as possible, this use case only focuses on setting up and using the value of the dry run parameter, which could be just one of many parameters in a complete protocol. + +#### Dry Run Definition + +First, we need to set up the dry run parameter. We want to set up a simple yes/no choice for the technician running the protocol, so we’ll use a Boolean parameter: + +``` +def add_parameters(parameters): + + parameters.add_bool( + variable_name="dry_run", + display_name="Dry Run", + description=( + "Skip delays," + " shorten mix steps," + " and return tips to their racks." + ), + default=False + ) + +``` + +This parameter is set to `False` by default, assuming that most runs will be live runs. In other words, during run setup the technician will have to change the parameter setting to perform a dry run. If they leave it as is, the robot will perform a live run. + +Additionally, since “dry run” can have different meanings in different contexts, it’s important to include a `description` that indicates exactly what the parameter will control — in this case, three things. The following sections will show how to accomplish each of those when the dry run parameter is set to `True`. + +#### Skipping Delays + +Many protocols have built\-in delays, either for a module to work or to let a reaction happen passively. Lengthy delays just get in the way when verifying a protocol with a dry run. So wherever the protocol calls for a delay, we can check the value of `protocol.params.dry_run` and make the protocol behave accordingly. + +To start, let’s consider a simple [`delay()`](index.html#opentrons.protocol_api.ProtocolContext.delay 'opentrons.protocol_api.ProtocolContext.delay') command. We can wrap it in an `if` statement such that the delay will only execute when the run is _not_ a dry run: + +``` +if protocol.params.dry_run is False: + protocol.delay(minutes=5) + +``` + +You can extend this approach to more complex situations, like module interactions. For example, in a protocol that moves a plate to the Thermocycler for an incubation, you’ll want to perform all the movement steps — opening and closing the module lid, and moving the plate to and from the block — but skip the heating and cooling time. The simplest way to do this is, like in the delay example above, to wrap each skippable command: + +``` +protocol.move_labware(labware=plate, new_location=tc_mod, use_gripper=True) +if protocol.params.dry_run is False: + tc_mod.set_block_temperature(4) + tc_mod.set_lid_temperature(100) +tc_mod.close_lid() +pcr_profile = [ + {"temperature": 68, "hold_time_seconds": 180}, + {"temperature": 98, "hold_time_seconds": 180}, +] +if protocol.params.dry_run is False: + tc_mod.execute_profile( + steps=pcr_profile, repetitions=1, block_max_volume=50 + ) +tc_mod.open_lid() + +``` + +#### Shortening Mix Steps + +Similar to delays, mix steps can take a long time because they are inherently repetitive actions. Mixing ten times takes ten times as long as mixing once! To save time, set a mix repetitions variable based on the value of `protocol.params.dry_run` and pass that to [`mix()`](index.html#opentrons.protocol_api.InstrumentContext.mix 'opentrons.protocol_api.InstrumentContext.mix'): + +``` +if protocol.params.dry_run is True: + mix_reps = 1 +else: + mix_reps = 10 +pipette.mix(repetitions=mix_reps, volume=50, location=plate["A1"].bottom()) + +``` + +Note that this checks whether the dry run parameter is `True`. If you prefer to set up all your `if` statements to check whether it’s `False`, you can reverse the logic: + +``` +if protocol.params.dry_run is False: + mix_reps = 10 +else: + mix_reps = 1 + +``` + +#### Returning Tips + +Tips used in a dry run should be reusable — for another dry run, if nothing else. It doesn’t make sense to dispose of them in a trash container, unless you specifically need to test movement to the trash. You can choose whether to use [`drop_tip()`](index.html#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip') or [`return_tip()`](index.html#opentrons.protocol_api.InstrumentContext.return_tip 'opentrons.protocol_api.InstrumentContext.return_tip') based on the value of `protocol.params.dry_run`. If the protocol doesn’t have too many tip drop actions, you can use an `if` statement each time: + +``` +if protocol.params.dry_run is True: + pipette.return_tip() +else: + pipette.drop_tip() + +``` + +However, repeating this block every time you handle tips could significantly clutter your code. Instead, you could define it as a function: + +``` +def return_or_drop(pipette): + if protocol.params.dry_run is True: + pipette.return_tip() + else: + pipette.drop_tip() + +``` + +Then call that function throughout your protocol: + +``` +pipette.pick_up_tip() +return_or_drop(pipette) + +``` + +Note + +It’s generally better to define a standalone function, rather than adding a method to the [`InstrumentContext`](index.html#opentrons.protocol_api.InstrumentContext 'opentrons.protocol_api.InstrumentContext') class. This makes your custom, parameterized commands stand out from API methods in your code. + +Additionally, if your protocol uses enough tips that you have to replenish tip racks, you’ll need separate behavior for dry runs and live runs. In a live run, once you’ve used all the tips, the rack is empty, because the tips are in the trash. In a dry run, once you’ve used all the tips in a rack, the rack is _full_, because you returned the tips. + +The API has methods to handle both of these situations. To continue using the same tip rack without physically replacing it, call [`reset_tipracks()`](index.html#opentrons.protocol_api.InstrumentContext.reset_tipracks 'opentrons.protocol_api.InstrumentContext.reset_tipracks'). In the live run, move the empty tip rack off the deck and move a full one into place: + +``` +if protocol.params.dry_run is True: + pipette.reset_tipracks() +else: + protocol.move_labware( + labware=tips_1, new_location=chute, use_gripper=True + ) + protocol.move_labware( + labware=tips_2, new_location="C3", use_gripper=True + ) + +``` + +You can modify this code for similar cases. You may be moving tip racks by hand, rather than with the gripper. Or you could even mix the two, moving the used (but full) rack off\-deck by hand — instead of dropping it down the chute, spilling all the tips — and have the gripper move a new rack into place. Ultimately, it’s up to you to fine\-tune your dry run behavior, and communicate it to your protocol’s users with your parameter descriptions. + +### Parameter Style Guide + +It’s important to write clear names and descriptions when you [define parameters](index.html#defining-rtp) in your protocols. Clarity improves the user experience for the technicians who run your protocols. They rely on your parameter names and descriptions to understand how the robot will function when running your protocol. + +Adopting the advice of this guide will help make your protocols clear, consistent, and ultimately easy to use. It also aligns them with protocols in the [Opentrons Protocol Library](https://library.opentrons.com), which can help others access and replicate your science. + +#### General Guidance + +**Parameter names are nouns.** Parameters should be discrete enough that you can describe them in a single word or short noun phrase. `display_name` is limited to 30 characters, and you can add more context in the description. + +Don’t ask questions or put other sentence punctuation in parameter names. For example: + +| ✅ Dry run | ❌ Dry run? | +| -------------------- | -------------------------------- | +| ✅ Sample count | ❌ How many samples? | +| ✅ Number of samples | ❌ Number of samples to process. | + +**Parameter descriptions explain actions.** In one or two clauses or sentences, state when and how the parameter value is used in the protocol. Don’t merely restate the parameter name. + +Punctuate descriptions as sentences, even if they aren’t complete sentences. For example: + +| Parameter name | Parameter description | +| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| Dry run | _ ✅ Skip incubation delays and shorten mix steps. _ ❌ Whether to do a dry run. | +| Aspirate volume | _ ✅ How much to aspirate from each sample. _ ❌ Volume that the pipette will aspirate | +| Dilution factor | _ ✅ Each step uses this ratio of total liquid to original solution. Express the ratio as a decimal. _ ❌ total/diluent ratio for the process | + +Not every parameter requires a description! For example, in a protocol that uses only one pipette, it would be difficult to explain a parameter named “Pipette type” without repeating yourself. In a protocol that offers parameters for two different pipettes, it may be useful to summarize what steps each pipette performs. + +**Use sentence case for readability**. Sentence case means adding a capital letter to _only_ the first word of the name and description. This gives your parameters a professional appearance. Keep proper names capitalized as they would be elsewhere in a sentence. For example: + +| ✅ Number of samples | ❌ number of samples | +| -------------------------- | -------------------------- | +| ✅ Temperature Module slot | ❌ Temperature module slot | +| ✅ Dilution factor | ❌ Dilution Factor | + +**Use numerals for all numbers.** In a scientific context, this includes single\-digit numbers. Additionally, punctuate numbers according to the needs of your protocol’s users. If you plan to share your protocol widely, consider using American English number punctuation (comma for thousands separator; period for decimal separator). + +**Order choices logically.** Place items within the `choices` attribute in the order that makes sense for your application. + +Numeric choices should either ascend or descend. Consider an offset parameter with choices. Sorting according to value is easy to use in either direction, but sorting by absolute value is difficult: + +| ✅ \-3, \-2, \-1, 0, 1, 2, 3 | ❌ 0, 1, \-1, 2, \-2, 3, \-3 | +| ---------------------------- | ---------------------------- | +| ✅ 3, 2, 1, 0, \-1, \-2, \-3 | | + +String choices may have an intrinsic ordering. If they don’t, fall back to alphabetical order. + +| Parameter name | Parameter description | +| -------------- | ------------------------------------------------------------------------------------------- | +| Liquid color | _ ✅ Red, Orange, Yellow, Green, Blue, Violet _ ❌ Blue, Green, Orange, Red, Violet, Yellow | +| Tube brand | _ ✅ Eppendorf, Falcon, Generic, NEST _ ❌ Falcon, NEST, Eppendorf, Generic | + +#### Type\-Specific Guidance + +##### Booleans + +The `True` value of a Boolean corresponds to the word _On_ and the `False` value corresponds to the word _Off_. + +**Avoid double negatives.** These are difficult to understand and may lead to a technician making an incorrect choice. Remember that negation can be part of a word’s meaning! For example, it’s difficult to reason about what will happen when a parameter named “Deactivate module” is set to “Off”. + +**When in doubt, clarify in the description.** If you feel like you need to add extra clarity to your Boolean choices, use the phrase “When on” or “When off” at the beginning of your description. For example, a parameter named “Dry run” could have the description “When on, skip protocol delays and return tips instead of trashing them.” + +##### Number Choices + +**Don’t repeat text in choices.** Rely on the name and description to indicate what the number refers to. It’s OK to add units to the display names of numeric choices, because the `unit` attribute is ignored when you specify `choices`. + +| Parameter name | Parameter description | +| ----------------- | -------------------------------------------------------------------------------------------------------------------- | +| Number of columns | _ ✅ 1, 2, 3 _ ❌ 1 column, 2 columns, 3 columns | +| Aspirate volume | _ ✅ 10 µL, 20 µL, 50 µL _ ✅ Low (10 µL), Medium (20 µL), High (50 µL) \* ❌ Low volume, Medium volume, High volume | + +**Use a range instead of choices when all values are acceptable.** It’s faster and easier to enter a numeric value than to choose from a long list. For example, a “Number of columns” parameter that accepts any number 1 through 12 should specify a `minimum` and `maximum`, rather than `choices`. However, if the application requires that the parameter only accepts even numbers, you need to specify choices (2, 4, 6, 8, 10, 12\). + +##### Strings + +**Avoid strings that are synonymous with “yes” and “no”.** When presenting exactly two string choices, consider their meaning. Can they be rephrased in terms of “yes/no”, “true/false”, or “on/off”? If no, then a string parameter is appropriate. If yes, it’s better to use a Boolean, which appears in run setup as a toggle rather than a dropdown menu. + +> - ✅ Blue, Red +> - ✅ Left\-to\-right, Right\-to\-left +> - ❌ Include, Exclude +> - ❌ Yes, No + +Runtime parameters let you define user\-customizable variables in your Python protocols. This gives you greater flexibility and puts extra control in the hands of the technician running the protocol — without forcing them to switch between lots of protocol files or write code themselves. + +This section begins with the fundamentals of runtime parameters: + +- Preliminary advice on how to [choose good parameters](index.html#good-rtps), before you start writing code. +- The syntax for [defining parameters](index.html#defining-rtp) with boolean, numeric, and string values. +- How to [use parameter values](index.html#using-rtp) in your protocol, building logic and API calls that implement the technician’s choices. + +It continues with a selection of use cases and some overall style guidance. When adding parameters, you are in charge of the user experience when it comes time to set up the protocol! These pages outline best practices for making your protocols reliable and easy to use. + +- [Use case – sample count](index.html#use-case-sample-count): Change behavior throughout a protocol based on how many samples you plan to process. Setting sample count exactly saves time, tips, and reagents. +- [Use case – dry run](index.html#use-case-dry-run): Test your protocol, rather than perform a live run, just by flipping a toggle. +- [Style and usage](index.html#rtp-style): When you’re a protocol author, you write code. When you’re a parameter author, you write words. Follow this advice to make things as clear as possible for the technicians who will run your protocol. + +## Advanced Control + +As its name implies, the Python Protocol API is primarily designed for creating protocols that you upload via the Opentrons App and execute on the robot as a unit. But sometimes it’s more convenient to control the robot outside of the app. For example, you might want to have variables in your code that change based on user input or the contents of a CSV file. Or you might want to only execute part of your protocol at a time, especially when developing or debugging a new protocol. + +The Python API offers two ways of issuing commands to the robot outside of the app: through Jupyter Notebook or on the command line with `opentrons_execute`. + +### Jupyter Notebook + +The Flex and OT\-2 run [Jupyter Notebook](https://jupyter.org) servers on port 48888, which you can connect to with your web browser. This is a convenient environment for writing and debugging protocols, since you can define different parts of your protocol in different notebook cells and run a single cell at a time. + +Access your robot’s Jupyter Notebook by either: + +- Going to the **Advanced** tab of Robot Settings and clicking **Launch Jupyter Notebook**. +- Going directly to `http://:48888` in your web browser (if you know your robot’s IP address). + +Once you’ve launched Jupyter Notebook, you can create a notebook file or edit an existing one. These notebook files are stored on the the robot. If you want to save code from a notebook to your computer, go to **File \> Download As** in the notebook interface. + +#### Protocol Structure + +Jupyter Notebook is structured around cells: discrete chunks of code that can be run individually. This is nearly the opposite of Opentrons protocols, which bundle all commands into a single `run` function. Therefore, to take full advantage of Jupyter Notebook, you have to restructure your protocol. + +Rather than writing a `run` function and embedding commands within it, start your notebook by importing `opentrons.execute` and calling [`opentrons.execute.get_protocol_api()`](index.html#opentrons.execute.get_protocol_api 'opentrons.execute.get_protocol_api'). This function also replaces the `metadata` block of a standalone protocol by taking the minimum [API version](index.html#v2-versioning) as its argument. Then you can call [`ProtocolContext`](index.html#opentrons.protocol_api.ProtocolContext 'opentrons.protocol_api.ProtocolContext') methods in subsequent lines or cells: + +``` +import opentrons.execute +protocol = opentrons.execute.get_protocol_api("2.19") +protocol.home() + +``` + +The first command you execute should always be [`home()`](index.html#opentrons.protocol_api.ProtocolContext.home 'opentrons.protocol_api.ProtocolContext.home'). If you try to execute other commands first, you will get a `MustHomeError`. (When running protocols through the Opentrons App, the robot homes automatically.) + +You should use the same [`ProtocolContext`](index.html#opentrons.protocol_api.ProtocolContext 'opentrons.protocol_api.ProtocolContext') throughout your notebook, unless you need to start over from the beginning of your protocol logic. In that case, call [`get_protocol_api()`](index.html#opentrons.execute.get_protocol_api 'opentrons.execute.get_protocol_api') again to get a new [`ProtocolContext`](index.html#opentrons.protocol_api.ProtocolContext 'opentrons.protocol_api.ProtocolContext'). + +#### Running a Previously Written Protocol + +You can also use Jupyter to run a protocol that you have already written. To do so, first copy the entire text of the protocol into a cell and run that cell: + +``` +import opentrons.execute +from opentrons import protocol_api +def run(protocol: protocol_api.ProtocolContext): + # the contents of your previously written protocol go here + +``` + +Since a typical protocol only defines the `run` function but doesn’t call it, this won’t immediately cause the robot to move. To begin the run, instantiate a [`ProtocolContext`](index.html#opentrons.protocol_api.ProtocolContext 'opentrons.protocol_api.ProtocolContext') and pass it to the `run` function you just defined: + +``` +protocol = opentrons.execute.get_protocol_api("2.19") +run(protocol) # your protocol will now run + +``` + +### Setting Labware Offsets + +All positions relative to labware are adjusted automatically based on labware offset data. When you’re running your code in Jupyter Notebook or with `opentrons_execute`, you need to set your own offsets because you can’t perform run setup and Labware Position Check in the Opentrons App or on the Flex touchscreen. + +#### Creating a Dummy Protocol + +For advanced control applications, do the following to calculate and apply labware offsets: + +> 1. Create a “dummy” protocol that loads your labware and has each used pipette pick up a tip from a tip rack. +> 2. Import the dummy protocol to the Opentrons App. +> 3. Run Labware Position Check from the app or touchscreen. +> 4. Add the offsets to your code with [`set_offset()`](index.html#opentrons.protocol_api.Labware.set_offset 'opentrons.protocol_api.Labware.set_offset'). + +Creating the dummy protocol requires you to: + +> 1. Use the `metadata` or `requirements` dictionary to specify the API version. (See [Versioning](index.html#v2-versioning) for details.) Use the same API version as you did in [`opentrons.execute.get_protocol_api()`](index.html#opentrons.execute.get_protocol_api 'opentrons.execute.get_protocol_api'). +> 2. Define a `run()` function. +> 3. Load all of your labware in their initial locations. +> 4. Load your smallest capacity pipette and specify its `tip_racks`. +> 5. Call `pick_up_tip()`. Labware Position Check can’t run if you don’t pick up a tip. + +For example, the following dummy protocol will use a P300 Single\-Channel GEN2 pipette to enable Labware Position Check for an OT\-2 tip rack, NEST reservoir, and NEST flat well plate. + +``` +metadata = {"apiLevel": "2.13"} + + def run(protocol): + tiprack = protocol.load_labware("opentrons_96_tiprack_300ul", 1) + reservoir = protocol.load_labware("nest_12_reservoir_15ml", 2) + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", 3) + p300 = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tiprack]) + p300.pick_up_tip() + p300.return_tip() + +``` + +After importing this protocol to the Opentrons App, run Labware Position Check to get the x, y, and z offsets for the tip rack and labware. When complete, you can click **Get Labware Offset Data** to view automatically generated code that uses [`set_offset()`](index.html#opentrons.protocol_api.Labware.set_offset 'opentrons.protocol_api.Labware.set_offset') to apply the offsets to each piece of labware. + +``` +labware_1 = protocol.load_labware("opentrons_96_tiprack_300ul", location="1") +labware_1.set_offset(x=0.00, y=0.00, z=0.00) + +labware_2 = protocol.load_labware("nest_12_reservoir_15ml", location="2") +labware_2.set_offset(x=0.10, y=0.20, z=0.30) + +labware_3 = protocol.load_labware("nest_96_wellplate_200ul_flat", location="3") +labware_3.set_offset(x=0.10, y=0.20, z=0.30) + +``` + +This automatically generated code uses generic names for the loaded labware. If you want to match the labware names already in your protocol, change the labware names to match your original code: + +``` +reservoir = protocol.load_labware("nest_12_reservoir_15ml", "2") +reservoir.set_offset(x=0.10, y=0.20, z=0.30) + +``` + +New in version 2\.12\. + +Once you’ve executed this code in Jupyter Notebook, all subsequent positional calculations for this reservoir in slot 2 will be adjusted 0\.1 mm to the right, 0\.2 mm to the back, and 0\.3 mm up. + +Keep in mind that `set_offset()` commands will override any labware offsets set by running Labware Position Check in the Opentrons App. And you should follow the behavior of Labware Position Check, i.e., _do not_ reuse offset measurements unless they apply to the _same labware type_ in the _same deck slot_ on the _same robot_. + +Warning + +Improperly reusing offset data may cause your robot to move to an unexpected position or crash against labware, which can lead to incorrect protocol execution or damage your equipment. When in doubt: run Labware Position Check again and update your code! + +#### Labware Offset Behavior + +How the API applies labware offsets varies depending on the API level of your protocol. This section describes the latest behavior. For details on how offsets work in earlier API versions, see the API reference entry for [`set_offset()`](index.html#opentrons.protocol_api.Labware.set_offset 'opentrons.protocol_api.Labware.set_offset'). + +In the latest API version, offsets apply to labware type–location combinations. For example, if you use `set_offset()` on a tip rack, use all the tips, and replace the rack with a fresh one of the same type in the same location, the offsets will apply to the fresh tip rack: + +``` +tiprack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", location="D3" +) +tiprack2 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + location=protocol_api.OFF_DECK, +) +tiprack.set_offset(x=0.1, y=0.1, z=0.1) +protocol.move_labware( + labware=tiprack, new_location=protocol_api.OFF_DECK +) # tiprack has no offset while off-deck +protocol.move_labware( + labware=tiprack2, new_location="D3" +) # tiprack2 now has offset 0.1, 0.1, 0.1 + +``` + +Because offsets apply to combinations of labware type and location, if you want an offset to apply to a piece of labware as it moves around the deck, call `set_offset()` again after each movement: + +``` +plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", location="D2" +) +plate.set_offset( + x=-0.1, y=-0.2, z=-0.3 +) # plate now has offset -0.1, -0.2, -0.3 +protocol.move_labware( + labware=plate, new_location="D3" +) # plate now has offset 0, 0, 0 +plate.set_offset( + x=-0.1, y=-0.2, z=-0.3 +) # plate again has offset -0.1, -0.2, -0.3 + +``` + +### Using Custom Labware + +If you have custom labware definitions you want to use with Jupyter, make a new directory called `labware` in Jupyter and put the definitions there. These definitions will be available when you call [`load_labware()`](index.html#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware'). + +### Using Modules + +If your protocol uses [modules](index.html#new-modules), you need to take additional steps to make sure that Jupyter Notebook doesn’t send commands that conflict with the robot server. Sending commands to modules while the robot server is running will likely cause errors, and the module commands may not execute as expected. + +To disable the robot server, open a Jupyter terminal session by going to **New \> Terminal** and run `systemctl stop opentrons-robot-server`. Then you can run code from cells in your notebook as usual. When you are done using Jupyter Notebook, you should restart the robot server with `systemctl start opentrons-robot-server`. + +Note + +While the robot server is stopped, the robot will display as unavailable in the Opentrons App. If you need to control the robot or its attached modules through the app, you need to restart the robot server and wait for the robot to appear as available in the app. + +### Command Line + +The robot’s command line is accessible either by going to **New \> Terminal** in Jupyter or [via SSH](https://support.opentrons.com/s/article/Connecting-to-your-OT-2-with-SSH). + +To execute a protocol from the robot’s command line, copy the protocol file to the robot with `scp` and then run the protocol with `opentrons_execute`: + +``` +opentrons_execute /data/my_protocol.py + +``` + +By default, `opentrons_execute` will print out the same run log shown in the Opentrons App, as the protocol executes. It also prints out internal logs at the level `warning` or above. Both of these behaviors can be changed. Run `opentrons_execute --help` for more information. + +## Protocol Examples + +This page provides simple, ready\-made protocols for Flex and OT\-2\. Feel free to copy and modify these examples to create unique protocols that help automate your laboratory workflows. Also, experimenting with these protocols is another way to build upon the skills you’ve learned from working through the [tutorial](index.html#tutorial). Try adding different hardware, labware, and commands to a sample protocol and test its validity after importing it into the Opentrons App. + +### Using These Protocols + +These sample protocols are designed for anyone using an Opentrons Flex or OT\-2 liquid handling robot. For our users with little to no Python experience, we’ve taken some liberties with the syntax and structure of the code to make it easier to understand. For example, we’ve formatted the samples with line breaks to show method arguments clearly and to avoid horizontal scrolling. Additionally, the methods use [named arguments](https://en.wikipedia.org/wiki/Named_parameter) instead of positional arguments. For example: + +``` +# This code uses named arguments +tiprack_1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + location="D2") + +# This code uses positional arguments +tiprack_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "D2") + +``` + +Both examples instantiate the variable `tiprack_1` with a Flex tip rack, but the former is more explicit. It shows the parameter name and its value together (e.g. `location="D2"`), which may be helpful when you’re unsure about what’s going on in a protocol code sample. + +Python developers with more experience should feel free to ignore the code styling used here and work with these examples as you like. + +### Instruments and Labware + +The sample protocols all use the following pipettes: + +- Flex 1\-Channel Pipette (5–1000 µL). The API load name for this pipette is `flex_1channel_1000`. +- P300 Single\-Channel GEN2 pipette for the OT\-2\. The API load name for this pipette is `p300_single_gen2`. + +They also use the labware listed below: + +| Labware type | Labware name | API load name | +| -------------- | --------------------------------------- | --------------------------------- | +| Reservoir | USA Scientific 12\-Well Reservoir 22 mL | `usascientific_12_reservoir_22ml` | +| Well plate | Corning 96\-Well Plate 360 µL Flat | `corning_96_wellplate_360ul_flat` | +| Flex tip rack | Opentrons Flex 96 Tip Rack 200 µL | `opentrons_flex_96_tiprack_200ul` | +| OT\-2 tip rack | Opentrons 96 Tip Rack 300 µL | `opentrons_96_tiprack_300ul` | + +### Protocol Template + +This code only loads the instruments and labware listed above, and performs no other actions. Many code snippets from elsewhere in the documentation will run without modification when added at the bottom of this template. You can also use it to start writing and testing your own code. + +### Flex + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # load tip rack in deck slot D3 + tiprack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", location="D3" + ) + # attach pipette to left mount + pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=[tiprack] + ) + # load well plate in deck slot D2 + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", location="D2" + ) + # load reservoir in deck slot D1 + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", location="D1" + ) + # load trash bin in deck slot A3 + trash = protocol.load_trash_bin(location="A3") + # Put protocol commands here + +``` + +### OT-2 + +``` +from opentrons import protocol_api + +metadata = {"apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # load tip rack in deck slot 3 + tiprack = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", location=3 + ) + # attach pipette to left mount + pipette = protocol.load_instrument( + instrument_name="p300_single_gen2", + mount="left", + tip_racks=[tiprack] + ) + # load well plate in deck slot 2 + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", location=2 + ) + # load reservoir in deck slot 1 + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", location=1 + ) + # Put protocol commands here + +``` + +### Transferring Liquids + +These protocols demonstrate how to move 100 µL of liquid from one well to another. + +#### Basic Method + +This protocol uses some [building block commands](index.html#v2-atomic-commands) to tell the robot, explicitly, where to go to aspirate and dispense liquid. These commands include the [`pick_up_tip()`](index.html#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip'), [`aspirate()`](index.html#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate'), and [`dispense()`](index.html#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') methods. + +### Flex + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel":"2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location="D1") + tiprack_1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + location="D2") + trash = protocol.load_trash_bin("A3") + pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=[tiprack_1]) + + pipette.pick_up_tip() + pipette.aspirate(100, plate["A1"]) + pipette.dispense(100, plate["B1"]) + pipette.drop_tip() + +``` + +### OT-2 + +``` +from opentrons import protocol_api + +metadata = {"apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location=1) + tiprack_1 = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=2) + p300 = protocol.load_instrument( + instrument_name="p300_single", + mount="left", + tip_racks=[tiprack_1]) + + p300.pick_up_tip() + p300.aspirate(100, plate["A1"]) + p300.dispense(100, plate["B1"]) + p300.drop_tip() + +``` + +#### Advanced Method + +This protocol accomplishes the same thing as the previous example, but does it a little more efficiently. Notice how it uses the [`InstrumentContext.transfer()`](index.html#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') method to move liquid between well plates. The source and destination well arguments (e.g., `plate["A1"], plate["B1"]`) are part of `transfer()` method parameters. You don’t need separate calls to `aspirate` or `dispense` here. + +### Flex + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location="D1") + tiprack_1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + location="D2") + trash = protocol.load_trash_bin("A3") + pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=[tiprack_1]) + # transfer 100 µL from well A1 to well B1 + pipette.transfer(100, plate["A1"], plate["B1"]) + +``` + +### OT-2 + +``` +from opentrons import protocol_api + +metadata = {"apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location=1) + tiprack_1 = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=2) + p300 = protocol.load_instrument( + instrument_name="p300_single", + mount="left", + tip_racks=[tiprack_1]) + # transfer 100 µL from well A1 to well B1 + p300.transfer(100, plate["A1"], plate["B1"]) + +``` + +### Loops + +In Python, a loop is an instruction that keeps repeating an action until a specific condition is met. + +When used in a protocol, loops automate repetitive steps such as aspirating and dispensing liquids from a reservoir to a a range of wells, or all the wells, in a well plate. For example, this code sample loops through the numbers 0 to 7, and uses the loop’s current value to transfer liquid from all the wells in a reservoir to all the wells in a 96\-well plate. + +### Flex + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel":"2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location="D1") + tiprack_1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + location="D2") + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", + location="D3") + trash = protocol.load_trash_bin("A3") + pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=[tiprack_1]) + + # distribute 20 µL from reservoir:A1 -> plate:row:1 + # distribute 20 µL from reservoir:A2 -> plate:row:2 + # etc... + # range() starts at 0 and stops before 8, creating a range of 0-7 + for i in range(8): + pipette.distribute(200, reservoir.wells()[i], plate.rows()[i]) + +``` + +### OT-2 + +``` +from opentrons import protocol_api + +metadata = {"apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location=1) + tiprack_1 = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=2) + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", + location=4) + p300 = protocol.load_instrument( + instrument_name="p300_single", + mount="left", + tip_racks=[tiprack_1]) + + # distribute 20 µL from reservoir:A1 -> plate:row:1 + # distribute 20 µL from reservoir:A2 -> plate:row:2 + # etc... + # range() starts at 0 and stops before 8, creating a range of 0-7 + for i in range(8): + p300.distribute(200, reservoir.wells()[i], plate.rows()[i]) + +``` + +Notice here how Python’s [`range`](https://docs.python.org/3/library/stdtypes.html#range '(in Python v3.12)') class (e.g., `range(8)`) determines how many times the code loops. Also, in Python, a range of numbers is _exclusive_ of the end value and counting starts at 0, not 1\. For the Corning 96\-well plate used here, this means well A1\=0, B1\=1, C1\=2, and so on to the last well in the row, which is H1\=7\. + +### Multiple Air Gaps + +Opentrons electronic pipettes can do some things that a human cannot do with a pipette, like accurately alternate between liquid and air aspirations that create gaps within the same tip. The protocol shown below shows you how to aspirate from the first five wells in the reservoir and create an air gap between each sample. + +### Flex + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel":"2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location="D1") + tiprack_1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + location="D2") + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", + location="D3") + trash = protocol.load_trash_bin("A3") + pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=[tiprack_1]) + + pipette.pick_up_tip() + + # aspirate from the first 5 wells + for well in reservoir.wells()[:5]: + pipette.aspirate(volume=35, location=well) + pipette.air_gap(10) + + pipette.dispense(225, plate["A1"]) + + pipette.return_tip() + +``` + +### OT-2 + +``` +from opentrons import protocol_api + +metadata = {"apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location=1) + tiprack_1 = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=2) + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", + location=3) + p300 = protocol.load_instrument( + instrument_name="p300_single", + mount="right", + tip_racks=[tiprack_1]) + + p300.pick_up_tip() + + # aspirate from the first 5 wells + for well in reservoir.wells()[:5]: + p300.aspirate(volume=35, location=well) + p300.air_gap(10) + + p300.dispense(225, plate["A1"]) + + p300.return_tip() + +``` + +Notice here how Python’s [`slice`](https://docs.python.org/3/library/functions.html#slice '(in Python v3.12)') functionality (in the code sample as `[:5]`) lets us select the first five wells of the well plate only. Also, in Python, a range of numbers is _exclusive_ of the end value and counting starts at 0, not 1\. For the USA Scientific 12\-well reservoir used here, this means well A1\=0, A2\=1, A3\=2, and so on to the last well used, which is A5\=4\. See also, the [Commands](index.html#tutorial-commands) section of the Tutorial. + +### Dilution + +This protocol dispenses diluent to all wells of a Corning 96\-well plate. Next, it dilutes 8 samples from the reservoir across all 8 columns of the plate. + +### Flex + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location="D1") + tiprack_1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + location="D2") + tiprack_2 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + location="D3") + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", + location="C1") + trash = protocol.load_trash_bin("A3") + pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="left", + tip_racks=[tiprack_1, tiprack_2]) + # Dispense diluent + pipette.distribute(50, reservoir["A12"], plate.wells()) + + # loop through each row + for i in range(8): + # save the source well and destination column to variables + source = reservoir.wells()[i] + row = plate.rows()[i] + + # transfer 30 µL of source to first well in column + pipette.transfer(30, source, row[0], mix_after=(3, 25)) + + # dilute the sample down the column + pipette.transfer( + 30, row[:11], row[1:], + mix_after=(3, 25)) + +``` + +### OT-2 + +``` +from opentrons import protocol_api + +metadata = {"apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location=1) + tiprack_1 = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=2) + tiprack_2 = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=3) + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", + location=4) + p300 = protocol.load_instrument( + instrument_name="p300_single", + mount="right", + tip_racks=[tiprack_1, tiprack_2]) + # Dispense diluent + p300.distribute(50, reservoir["A12"], plate.wells()) + + # loop through each row + for i in range(8): + # save the source well and destination column to variables + source = reservoir.wells()[i] + source = reservoir.wells()[i] + row = plate.rows()[i] + + # transfer 30 µL of source to first well in column + p300.transfer(30, source, row[0], mix_after=(3, 25)) + + # dilute the sample down the column + p300.transfer( + 30, row[:11], row[1:], + mix_after=(3, 25)) + +``` + +Notice here how the code sample loops through the rows and uses slicing to distribute the diluent. For information about these features, see the Loops and Air Gaps examples above. See also, the [Commands](index.html#tutorial-commands) section of the Tutorial. + +### Plate Mapping + +This protocol dispenses different volumes of liquids to a well plate and automatically refills the pipette when empty. + +### Flex + +``` +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location="D1") + tiprack_1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + location="D2") + tiprack_2 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + location="D3") + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", + location="C1") + trash = protocol.load_trash_bin("A3") + pipette = protocol.load_instrument( + instrument_name="flex_1channel_1000", + mount="right", + tip_racks=[tiprack_1, tiprack_2]) + + # Volume amounts are for demonstration purposes only + water_volumes = [ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96 + ] + + pipette.distribute(water_volumes, reservoir["A12"], plate.wells()) + +``` + +### OT-2 + +``` +from opentrons import protocol_api +metadata = {"apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + plate = protocol.load_labware( + load_name="corning_96_wellplate_360ul_flat", + location=1) + tiprack_1 = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=2) + tiprack_2 = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=3) + reservoir = protocol.load_labware( + load_name="usascientific_12_reservoir_22ml", + location=4) + p300 = protocol.load_instrument( + instrument_name="p300_single", + mount="right", + tip_racks=[tiprack_1, tiprack_2]) + + # Volume amounts are for demonstration purposes only + water_volumes = [ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96 + ] + + p300.distribute(water_volumes, reservoir["A12"], plate.wells()) + +``` + +## Adapting OT\-2 Protocols for Flex + +Python protocols designed to run on the OT\-2 can’t be directly run on Flex without some modifications. This page describes the minimal steps that you need to take to get OT\-2 protocols analyzing and running on Flex. + +Adapting a protocol for Flex lets you have parity across different Opentrons robots in your lab, or you can extend older protocols to take advantage of new features only available on Flex. Depending on your application, you may need to do additional verification of your adapted protocol. + +Examples on this page are in tabs so you can quickly move back and forth to see the differences between OT\-2 and Flex code. + +### Metadata and Requirements + +Flex requires you to specify an `apiLevel` of 2\.15 or higher. If your OT\-2 protocol specified `apiLevel` in the `metadata` dictionary, it’s best to move it to the `requirements` dictionary. You can’t specify it in both places, or the API will raise an error. + +Note + +Consult the [list of changes in API versions](index.html#version-notes) to see what effect raising the `apiLevel` will have. If you increased it by multiple minor versions to get your protocol running on Flex, make sure that your protocol isn’t using removed commands or commands whose behavior has changed in a way that may affect your scientific results. + +You also need to specify `"robotType": "Flex"`. If you omit `robotType` in the `requirements` dictionary, the API will assume the protocol is designed for the OT\-2\. + +### Original OT-2 code + +``` +from opentrons import protocol_api + +metadata = { + "protocolName": "My Protocol", + "description": "This protocol uses the OT-2", + "apiLevel": "2.19" +} + +``` + +### Updated Flex code + +``` +from opentrons import protocol_api + +metadata = { + "protocolName": "My Protocol", + "description": "This protocol uses the Flex", +} + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +``` + +### Pipettes and Tip\-rack Load Names + +Flex uses different types of pipettes and tip racks than OT\-2, which have their own load names in the API. If possible, load Flex pipettes of the same capacity or larger than the OT\-2 pipettes. See the [list of pipette API load names](index.html#new-pipette-models) for the valid values of `instrument_name` in Flex protocols. And check [Labware Library](https://labware.opentrons.com) or the Opentrons App for the load names of Flex tip racks. + +Note + +If you use smaller capacity tips than in the OT\-2 protocol, you may need to make further adjustments to avoid running out of tips. Also, the protocol may have more steps and take longer to execute. + +This example converts OT\-2 code that uses a P300 Single\-Channel GEN2 pipette and 300 µL tips to Flex code that uses a Flex 1\-Channel 1000 µL pipette and 1000 µL tips. + +### Original OT-2 code + +``` +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_96_tiprack_300ul", 1) + left_pipette = protocol.load_instrument( + "p300_single_gen2", "left", tip_racks=[tips] + ) + +``` + +### Updated Flex code + +``` +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "D1") + left_pipette = protocol.load_instrument( + "flex_1channel_1000", "left", tip_racks[tips] + ) + +``` + +### Trash Container + +OT\-2 protocols always have a [`fixed_trash`](index.html#opentrons.protocol_api.ProtocolContext.fixed_trash 'opentrons.protocol_api.ProtocolContext.fixed_trash') in slot 12\. In Flex protocols specifying API version 2\.16 or later, you need to [load a trash bin](index.html#configure-trash-bin). Put it in slot A3 to match the physical position of the OT\-2 fixed trash: + +``` +trash = protocol.load_trash_bin("A3") + +``` + +### Deck Slot Labels + +It’s good practice to update numeric labels for [deck slots](index.html#deck-slots) (which match the labels on an OT\-2\) to coordinate ones (which match the labels on Flex). This is an optional step, since the two formats are interchangeable. + +For example, the code in the previous section changed the location of the tip rack from `1` to `"D1"`. + +### Module Load Names + +If your OT\-2 protocol uses older generations of the Temperature Module or Thermocycler Module, update the load names you pass to [`load_module()`](index.html#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module') to ones compatible with Flex: + +> - `temperature module gen2` +> - `thermocycler module gen2` or `thermocyclerModuleV2` + +The Heater\-Shaker Module only has one generation, `heaterShakerModuleV1`, which is compatible with Flex and OT\-2\. + +The Magnetic Module is not compatible with Flex. For protocols that load `magnetic module`, `magdeck`, or `magnetic module gen2`, you will need to make further modifications to use the [Magnetic Block](index.html#magnetic-block) and Flex Gripper instead. This will require reworking some of your protocol steps, and you should verify that your new protocol design achieves similar results. + +This simplified example, taken from a DNA extraction protocol, shows how using the Flex Gripper and the Magnetic Block can save time. Instead of pipetting an entire plate’s worth of liquid from the Heater\-Shaker to the Magnetic Module and then engaging the module, the gripper moves the plate to the Magnetic Block in one step. + +### Original OT-2 code + +``` +hs_mod.set_and_wait_for_shake_speed(2000) +protocol.delay(minutes=5) +hs_mod.deactivate_shaker() + +for i in sample_plate.wells(): + # mix, transfer, and blow-out all samples + pipette.pick_up_tip() + pipette.aspirate(100,hs_plate[i]) + pipette.dispense(100,hs_plate[i]) + pipette.aspirate(100,hs_plate[i]) + pipette.air_gap(10) + pipette.dispense(pipette.current_volume,mag_plate[i]) + pipette.aspirate(50,hs_plate[i]) + pipette.air_gap(10) + pipette.dispense(pipette.current_volume,mag_plate[i]) + pipette.blow_out(mag_plate[i].bottom(0.5)) + pipette.drop_tip() + +mag_mod.engage() + +# perform elution steps + +``` + +### Updated Flex code + +``` +hs_mod.set_and_wait_for_shake_speed(2000) +protocol.delay(minutes=5) +hs_mod.deactivate_shaker() + +# move entire plate +# no pipetting from Heater-Shaker needed +hs_mod.open_labware_latch() +protocol.move_labware(sample_plate, mag_block, use_gripper=True) + +# perform elution steps + +``` + +The Opentrons Python Protocol API is a Python framework designed to make it easy to write automated biology lab protocols. Python protocols can control Opentrons Flex and OT\-2 robots, their pipettes, and optional hardware modules. We’ve designed the API to be accessible to anyone with basic Python and wet\-lab skills. + +As a bench scientist, you should be able to code your protocols in a way that reads like a lab notebook. You can write a fully functional protocol just by listing the equipment you’ll use (modules, labware, and pipettes) and the exact sequence of movements the robot should make. + +As a programmer, you can leverage the full power of Python for advanced automation in your protocols. Perform calculations, manage external data, use built\-in and imported Python modules, and more to implement your custom lab workflow. + +## Getting Started + +**New to Python protocols?** Check out the [Tutorial](index.html#tutorial) to learn about the different parts of a protocol file and build a working protocol from scratch. + +If you want to **dive right into code**, take a look at our [Protocol Examples](index.html#new-examples) and the comprehensive [API Version 2 Reference](index.html#protocol-api-reference). + +When you’re ready to **try out a protocol**, download the [Opentrons App](https://www.opentrons.com/ot-app), import the protocol file, and run it on your robot. + +## How the API Works + +The design goal of this API is to make code readable and easy to understand. A protocol, in its most basic form: + +1. Provides some information about who made the protocol and what it is for. +2. Specifies which type of robot the protocol should run on. +3. Tells the robot where to find labware, pipettes, and (optionally) hardware modules. +4. Commands the robot to manipulate its attached hardware. + +For example, if we wanted to transfer liquid from well A1 to well B1 on a plate, our protocol would look like: + +### Flex + +``` +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "My Protocol", + "author": "Name ", + "description": "Simple protocol to get started using the Flex", +} + +# requirements +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +# protocol run function +def run(protocol: protocol_api.ProtocolContext): + # labware + plate = protocol.load_labware( + "corning_96_wellplate_360ul_flat", location="D1" + ) + tiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_200ul", location="D2" + ) + trash = protocol.load_trash_bin(location="A3") + + # pipettes + left_pipette = protocol.load_instrument( + "flex_1channel_1000", mount="left", tip_racks=[tiprack] + ) + + # commands + left_pipette.pick_up_tip() + left_pipette.aspirate(100, plate["A1"]) + left_pipette.dispense(100, plate["B2"]) + left_pipette.drop_tip() + +``` + +This example proceeds completely linearly. Following it line\-by\-line, you can see that it has the following effects: + +1. Gives the name, contact information, and a brief description for the protocol. +2. Indicates the protocol should run on a Flex robot, using API version 2\.19\. +3. Tells the robot that there is: + 1. A 96\-well flat plate in slot D1\. + 2. A rack of 300 µL tips in slot D2\. + 3. A 1\-channel 1000 µL pipette attached to the left mount, which should pick up tips from the aforementioned rack. +4. Tells the robot to act by: + 1. Picking up the first tip from the tip rack. + 2. Aspirating 100 µL of liquid from well A1 of the plate. + 3. Dispensing 100 µL of liquid into well B1 of the plate. + 4. Dropping the tip in the trash. + +### OT-2 + +``` +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "My Protocol", + "author": "Name ", + "description": "Simple protocol to get started using the OT-2", +} + +# requirements +requirements = {"robotType": "OT-2", "apiLevel": "2.19"} + +# protocol run function +def run(protocol: protocol_api.ProtocolContext): + # labware + plate = protocol.load_labware( + "corning_96_wellplate_360ul_flat", location="1" + ) + tiprack = protocol.load_labware( + "opentrons_96_tiprack_300ul", location="2" + ) + + # pipettes + left_pipette = protocol.load_instrument( + "p300_single", mount="left", tip_racks=[tiprack] + ) + + # commands + left_pipette.pick_up_tip() + left_pipette.aspirate(100, plate["A1"]) + left_pipette.dispense(100, plate["B2"]) + left_pipette.drop_tip() + +``` + +This example proceeds completely linearly. Following it line\-by\-line, you can see that it has the following effects: + +1. Gives the name, contact information, and a brief description for the protocol. +2. Indicates the protocol should run on an OT\-2 robot, using API version 2\.19\. +3. Tells the robot that there is: + 1. A 96\-well flat plate in slot 1\. + 2. A rack of 300 µL tips in slot 2\. + 3. A single\-channel 300 µL pipette attached to the left mount, which should pick up tips from the aforementioned rack. +4. Tells the robot to act by: + 1. Picking up the first tip from the tip rack. + 2. Aspirating 100 µL of liquid from well A1 of the plate. + 3. Dispensing 100 µL of liquid into well B1 of the plate. + 4. Dropping the tip in the trash. + +There is much more that Opentrons robots and the API can do! The [Building Block Commands](index.html#v2-atomic-commands), [Complex Commands](index.html#v2-complex-commands), and [Hardware Modules](index.html#new-modules) pages cover many of these functions. + +## More Resources + +### Opentrons App + +The [Opentrons App](https://opentrons.com/ot-app/) is the easiest way to run your Python protocols. The app runs on the latest versions of macOS, Windows, and Ubuntu. + +### Support + +Questions about setting up your robot, using Opentrons software, or troubleshooting? Check out our [support articles](https://support.opentrons.com/s/) or [contact Opentrons Support directly](mailto:support%40opentrons.com). + +### Custom Protocol Service + +Don’t have the time or resources to write your own protocols? Our [custom protocol development service](https://opentrons.com/instrument-services/) can get you set up in two weeks. + +### Contributing + +Opentrons software, including the Python API and this documentation, is open source. If you have an improvement or an interesting idea, you can create an issue on GitHub by following our [guidelines](https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md#opening-issues). + +That guide also includes more information on how to [directly contribute code](https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md). diff --git a/opentrons-ai-server/api/data/python_api_219_reference.md b/opentrons-ai-server/api/data/python_api_219_reference.md new file mode 100644 index 00000000000..be4b965abc7 --- /dev/null +++ b/opentrons-ai-server/api/data/python_api_219_reference.md @@ -0,0 +1,3313 @@ +## API Version 2 Reference + +### Protocols + +_class_ opentrons.protocol*api.ProtocolContext(\_api_version: APIVersion*, _core: AbstractProtocol\[AbstractInstrument\[AbstractWellCore], AbstractLabware\[AbstractWellCore], AbstractModuleCore]_, _broker: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[LegacyBroker] \= None_, _core_map: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[LoadedCoreMap] \= None_, _deck: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[Deck] \= None_, _bundled_data: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[Dict](https://docs.python.org/3/library/typing.html#typing.Dict '(in Python v3.12)')\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)'), [bytes](https://docs.python.org/3/library/stdtypes.html#bytes '(in Python v3.12)')]] \= None_) +A context for the state of a protocol. + +The `ProtocolContext` class provides the objects, attributes, and methods that +allow you to configure and control the protocol. + +Methods generally fall into one of two categories. + +> - They can change the state of the `ProtocolContext` object, such as adding +> pipettes, hardware modules, or labware to your protocol. +> - They can control the flow of a running protocol, such as pausing, displaying +> messages, or controlling built\-in robot hardware like the ambient lighting. + +Do not instantiate a `ProtocolContext` directly. +The `run()` function of your protocol does that for you. +See the [Tutorial](index.html#run-function) for more information. + +Use [`opentrons.execute.get_protocol_api()`](#opentrons.execute.get_protocol_api 'opentrons.execute.get_protocol_api') to instantiate a `ProtocolContext` when +using Jupyter Notebook. See [Advanced Control](index.html#advanced-control). + +New in version 2\.0\. + +_property_ api_version*: APIVersion* +Return the API version specified for this protocol context. + +This value is set when the protocol context +is initialized. + +> - When the context is the argument of `run()`, the `"apiLevel"` key of the +> [metadata](index.html#tutorial-metadata) or [requirements](index.html#tutorial-requirements) dictionary determines `api_version`. +> - When the context is instantiated with +> [`opentrons.execute.get_protocol_api()`](#opentrons.execute.get_protocol_api 'opentrons.execute.get_protocol_api') or +> [`opentrons.simulate.get_protocol_api()`](#opentrons.simulate.get_protocol_api 'opentrons.simulate.get_protocol_api'), the value of its `version` +> argument determines `api_version`. + +It may be lower than the [maximum version](index.html#max-version) supported by the +robot software, which is accessible via the +`protocol_api.MAX_SUPPORTED_VERSION` constant. + +New in version 2\.0\. + +_property_ bundled_data*: [Dict](https://docs.python.org/3/library/typing.html#typing.Dict '(in Python v3.12)')\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)'), [bytes](https://docs.python.org/3/library/stdtypes.html#bytes '(in Python v3.12)')]* +Accessor for data files bundled with this protocol, if any. + +This is a dictionary mapping the filenames of bundled datafiles to their +contents. The filename keys are formatted with extensions but without paths. For +example, a file stored in the bundle as `data/mydata/aspirations.csv` will +have the key `"aspirations.csv"`. The values are [`bytes`](https://docs.python.org/3/library/stdtypes.html#bytes '(in Python v3.12)') objects +representing the contents of the files. + +New in version 2\.0\. + +commands(_self_) → 'List\[str]' +Return the run log. + +This is a list of human\-readable strings representing what’s been done in the protocol so +far. For example, “Aspirating 123 µL from well A1 of 96 well plate in slot 1\.” + +The exact format of these entries is not guaranteed. The format here may differ from other +places that show the run log, such as the Opentrons App or touchscreen. + +New in version 2\.0\. + +comment(_self_, _msg: 'str'_) → 'None' +Add a user\-readable message to the run log. + +The message is visible anywhere you can view the run log, including the Opentrons App and the touchscreen on Flex. + +Note + +The value of the message is computed during protocol analysis, +so `comment()` can’t communicate real\-time information during the +actual protocol run. + +New in version 2\.0\. + +_property_ deck*: Deck* +An interface to provide information about what’s currently loaded on the deck. +This object is useful for determining if a slot on the deck is free. + +This object behaves like a dictionary whose keys are the [deck slot](index.html#deck-slots) names. +For instance, `deck[1]`, `deck["1"]`, and `deck["D1"]` +will all return the object loaded in the front\-left slot. + +The value for each key depends on what is loaded in the slot:\* A [`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') if the slot contains a labware. + +- A module context if the slot contains a hardware module. +- `None` if the slot doesn’t contain anything. + +A module that occupies multiple slots is set as the value for all of the +relevant slots. Currently, the only multiple\-slot module is the Thermocycler. +When loaded, the [`ThermocyclerContext`](#opentrons.protocol_api.ThermocyclerContext 'opentrons.protocol_api.ThermocyclerContext') object is the value for +`deck` keys `"A1"` and `"B1"` on Flex, and `7`, `8`, `10`, and +`11` on OT\-2\. In API version 2\.13 and earlier, only slot 7 keyed to the +Thermocycler object, and slots 8, 10, and 11 keyed to `None`. + +Rather than filtering the objects in the deck map yourself, +you can also use [`loaded_labwares`](#opentrons.protocol_api.ProtocolContext.loaded_labwares 'opentrons.protocol_api.ProtocolContext.loaded_labwares') to get a dict of labwares +and [`loaded_modules`](#opentrons.protocol_api.ProtocolContext.loaded_modules 'opentrons.protocol_api.ProtocolContext.loaded_modules') to get a dict of modules. + +For [Advanced Control](index.html#advanced-control) _only_, you can delete an element of the `deck` dict. +This only works for deck slots that contain labware objects. For example, if slot +1 contains a labware, `del protocol.deck["1"]` will free the slot so you can +load another labware there. + +Warning + +Deleting labware from a deck slot does not pause the protocol. Subsequent +commands continue immediately. If you need to physically move the labware to +reflect the new deck state, add a [`pause()`](#opentrons.protocol_api.ProtocolContext.pause 'opentrons.protocol_api.ProtocolContext.pause') or use +[`move_labware()`](#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') instead. + +Changed in version 2\.14: Includes the Thermocycler in all of the slots it occupies. + +Changed in version 2\.15: `del` sets the corresponding labware’s location to `OFF_DECK`. + +New in version 2\.0\. + +define*liquid(\_self*, _name: 'str'_, _description: 'Optional\[str]'_, _display_color: 'Optional\[str]'_) → 'Liquid' +Define a liquid within a protocol. + +Parameters: + +- **name** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – A human\-readable name for the liquid. +- **description** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional description of the liquid. +- **display_color** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional hex color code, with hash included, to represent the specified liquid. Standard three\-value, four\-value, six\-value, and eight\-value syntax are all acceptable. + +Returns: +A [`Liquid`](#opentrons.protocol_api.Liquid 'opentrons.protocol_api.Liquid') object representing the specified liquid. + +New in version 2\.14\. + +delay(_self_, _seconds: 'float' \= 0_, _minutes: 'float' \= 0_, _msg: 'Optional\[str]' \= None_) → 'None' +Delay protocol execution for a specific amount of time. + +Parameters: + +- **seconds** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The time to delay in seconds. +- **minutes** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The time to delay in minutes. + +If both `seconds` and `minutes` are specified, they will be added together. + +New in version 2\.0\. + +_property_ door_closed*: [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')* +Returns `True` if the front door of the robot is closed. + +New in version 2\.5\. + +_property_ fixed_trash*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware'), [TrashBin](index.html#opentrons.protocol_api.TrashBin 'opentrons.protocol_api.disposal_locations.TrashBin')]* +The trash fixed to slot 12 of an OT\-2’s deck. + +In API version 2\.15 and earlier, the fixed trash is a [`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') object with one well. Access it like labware in your protocol. For example, `protocol.fixed_trash["A1"]`. + +In API version 2\.15 only, Flex protocols have a fixed trash in slot A3\. + +In API version 2\.16 and later, the fixed trash only exists in OT\-2 protocols. It is a [`TrashBin`](#opentrons.protocol_api.TrashBin 'opentrons.protocol_api.TrashBin') object, which doesn’t have any wells. Trying to access `fixed_trash` in a Flex protocol will raise an error. See [Trash Bin](index.html#configure-trash-bin) for details on using the movable trash in Flex protocols. + +Changed in version 2\.16: Returns a `TrashBin` object. + +New in version 2\.0\. + +home(_self_) → 'None' +Home the movement system of the robot. + +New in version 2\.0\. + +is*simulating(\_self*) → 'bool' +Returns `True` if the protocol is running in simulation. + +Returns `False` if the protocol is running on actual hardware. + +You can evaluate the result of this method in an `if` statement to make your +protocol behave differently in different environments. For example, you could +refer to a data file on your computer when simulating and refer to a data file +stored on the robot when not simulating. + +You can also use it to skip time\-consuming aspects of your protocol. Most Python +Protocol API methods, like [`delay()`](#opentrons.protocol_api.ProtocolContext.delay 'opentrons.protocol_api.ProtocolContext.delay'), are designed to evaluate +instantaneously in simulation. But external methods, like those from the +[`time`](https://docs.python.org/3/library/time.html#module-time '(in Python v3.12)') module, will run at normal speed if not skipped. + +New in version 2\.0\. + +load*adapter(\_self*, _load_name: 'str'_, _location: 'Union\[DeckLocation, OffDeckType]'_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' +Load an adapter onto a location. + +For adapters already defined by Opentrons, this is a convenient way +to collapse the two stages of adapter initialization (creating +the adapter and adding it to the protocol) into one. + +This function returns the created and initialized adapter for use +later in the protocol. + +Parameters: + +- **load_name** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – A string to use for looking up a labware definition for the adapter. + You can find the `load_name` for any standard adapter on the Opentrons + [Labware Library](https://labware.opentrons.com). +- **location** (int or str or [`OFF_DECK`](#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK')) – Either a [deck slot](index.html#deck-slots), + like `1`, `"1"`, or `"D1"`, or the special value [`OFF_DECK`](#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK'). +- **namespace** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – The namespace that the labware definition belongs to. + If unspecified, the API will automatically search two namespaces: + +> - `"opentrons"`, to load standard Opentrons labware definitions. +> - `"custom_beta"`, to load custom labware definitions created with the +> [Custom Labware Creator](https://labware.opentrons.com/create). + +You might need to specify an explicit `namespace` if you have a custom +definition whose `load_name` is the same as an Opentrons standard +definition, and you want to explicitly choose one or the other. + +- **version** – The version of the labware definition. You should normally + leave this unspecified to let `load_adapter()` choose a version automatically. + +New in version 2\.15\. + +load*adapter_from_definition(\_self*, _adapter_def: "'LabwareDefinition'"_, _location: 'Union\[DeckLocation, OffDeckType]'_) → 'Labware' +Specify the presence of an adapter on the deck. + +This function loads the adapter definition specified by `adapter_def` +to the location specified by `location`. + +Parameters: + +- **adapter_def** – The adapter’s labware definition. +- **location** (int or str or [`OFF_DECK`](#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK')) – The slot into which to load the labware, + such as `1`, `"1"`, or `"D1"`. See [Deck Slots](index.html#deck-slots). + +New in version 2\.15\. + +load*instrument(\_self*, _instrument_name: 'str'_, _mount: 'Union\[Mount, str, None]' \= None_, _tip_racks: 'Optional\[List\[Labware]]' \= None_, _replace: 'bool' \= False_, _liquid_presence_detection: 'Optional\[bool]' \= None_) → 'InstrumentContext' +Load a specific instrument for use in the protocol. + +When analyzing the protocol on the robot, instruments loaded with this method +are compared against the instruments attached to the robot. You won’t be able to +start the protocol until the correct instruments are attached and calibrated. + +Currently, this method only loads pipettes. You do not need to load the Flex +Gripper to use it in protocols. See [Automatic vs Manual Moves](index.html#automatic-manual-moves). + +Parameters: + +- **instrument_name** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – The instrument to load. See [API Load Names](index.html#new-pipette-models) + for the valid values. +- **mount** (types.Mount or str or `None`) – The mount where the instrument should be attached. + This can either be an instance of [`types.Mount`](#opentrons.types.Mount 'opentrons.types.Mount') or one + of the strings `"left"` or `"right"`. When loading a Flex + 96\-Channel Pipette (`instrument_name="flex_96channel_1000"`), + you can leave this unspecified, since it always occupies both + mounts; if you do specify a value, it will be ignored. +- **tip_racks** (List\[[`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware')]) – A list of tip racks from which to pick tips when calling + [`InstrumentContext.pick_up_tip()`](#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') without arguments. +- **replace** ([_bool_](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')) – If `True`, replace the currently loaded instrument in + `mount`, if any. This is intended for [advanced + control](index.html#advanced-control) applications. You cannot + replace an instrument in the middle of a protocol being run + from the Opentrons App or touchscreen. +- **liquid_presence_detection** ([_bool_](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')) – If `True`, enable liquid presence detection for instrument. Only available on Flex robots in API Version 2\.20 and above. + +New in version 2\.0\. + +load*labware(\_self*, _load_name: 'str'_, _location: 'Union\[DeckLocation, OffDeckType]'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_, _adapter: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto a location. + +For Opentrons\-verified labware, this is a convenient way +to collapse the two stages of labware initialization (creating +the labware and adding it to the protocol) into one. + +This function returns the created and initialized labware for use +later in the protocol. + +Parameters: + +- **load_name** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – A string to use for looking up a labware definition. + You can find the `load_name` for any Opentrons\-verified labware on the + [Labware Library](https://labware.opentrons.com). +- **location** (int or str or [`OFF_DECK`](#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK')) – Either a [deck slot](index.html#deck-slots), + like `1`, `"1"`, or `"D1"`, or the special value [`OFF_DECK`](#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK'). + +Changed in version 2\.15: You can now specify a deck slot as a coordinate, like `"D1"`. + +- **label** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional special name to give the labware. If specified, + this is how the labware will appear in the run log, Labware Position + Check, and elsewhere in the Opentrons App and on the touchscreen. +- **namespace** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – The namespace that the labware definition belongs to. + If unspecified, the API will automatically search two namespaces: + +> - `"opentrons"`, to load standard Opentrons labware definitions. +> - `"custom_beta"`, to load custom labware definitions created with the +> [Custom Labware Creator](https://labware.opentrons.com/create). + +You might need to specify an explicit `namespace` if you have a custom +definition whose `load_name` is the same as an Opentrons\-verified +definition, and you want to explicitly choose one or the other. + +- **version** – The version of the labware definition. You should normally + leave this unspecified to let `load_labware()` choose a version + automatically. +- **adapter** – An adapter to load the labware on top of. Accepts the same + values as the `load_name` parameter of [`load_adapter()`](#opentrons.protocol_api.ProtocolContext.load_adapter 'opentrons.protocol_api.ProtocolContext.load_adapter'). The + adapter will use the same namespace as the labware, and the API will + choose the adapter’s version automatically. + +> New in version 2\.15\. + +New in version 2\.0\. + +load*labware_by_name(\_self*, _load_name: 'str'_, _location: 'DeckLocation'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'int' \= 1_) → 'Labware' + +Deprecated since version 2\.0: Use [`load_labware()`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') instead. + +New in version 2\.0\. + +load*labware_from_definition(\_self*, _labware_def: "'LabwareDefinition'"_, _location: 'Union\[DeckLocation, OffDeckType]'_, _label: 'Optional\[str]' \= None_) → 'Labware' +Specify the presence of a labware on the deck. + +This function loads the labware definition specified by `labware_def` +to the location specified by `location`. + +Parameters: + +- **labware_def** – The labware’s definition. +- **location** (int or str or [`OFF_DECK`](#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK')) – The slot into which to load the labware, + such as `1`, `"1"`, or `"D1"`. See [Deck Slots](index.html#deck-slots). +- **label** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional special name to give the labware. If specified, + this is how the labware will appear in the run log, Labware Position + Check, and elsewhere in the Opentrons App and on the touchscreen. + +New in version 2\.0\. + +load*module(\_self*, _module_name: 'str'_, _location: 'Optional\[DeckLocation]' \= None_, _configuration: 'Optional\[str]' \= None_) → 'ModuleTypes' +Load a module onto the deck, given its name or model. + +This is the function to call to use a module in your protocol, like +[`load_instrument()`](#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument') is the method to call to use an instrument +in your protocol. It returns the created and initialized module +context, which will be a different class depending on the kind of +module loaded. + +After loading modules, you can access a map of deck positions to loaded modules +with [`loaded_modules`](#opentrons.protocol_api.ProtocolContext.loaded_modules 'opentrons.protocol_api.ProtocolContext.loaded_modules'). + +Parameters: + +- **module_name** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – The name or model of the module. + See [Available Modules](index.html#available-modules) for possible values. +- **location** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)') _or_ [_int_](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)') _or_ _None_) – The location of the module. + +This is usually the name or number of the slot on the deck where you +will be placing the module, like `1`, `"1"`, or `"D1"`. See [Deck Slots](index.html#deck-slots). + +The Thermocycler is only valid in one deck location. +You don’t have to specify a location when loading it, but if you do, +it must be `7`, `"7"`, or `"B1"`. See [Thermocycler Module](index.html#thermocycler-module). + +Changed in version 2\.15: You can now specify a deck slot as a coordinate, like `"D1"`. + +- **configuration** – Configure a Thermocycler to be in the `semi` position. + This parameter does not work. Do not use it. + +Changed in version 2\.14: This parameter dangerously modified the protocol’s geometry system, +and it didn’t function properly, so it was removed. + +Returns: +The loaded and initialized module—a +[`HeaterShakerContext`](#opentrons.protocol_api.HeaterShakerContext 'opentrons.protocol_api.HeaterShakerContext'), +[`MagneticBlockContext`](#opentrons.protocol_api.MagneticBlockContext 'opentrons.protocol_api.MagneticBlockContext'), +[`MagneticModuleContext`](#opentrons.protocol_api.MagneticModuleContext 'opentrons.protocol_api.MagneticModuleContext'), +[`TemperatureModuleContext`](#opentrons.protocol_api.TemperatureModuleContext 'opentrons.protocol_api.TemperatureModuleContext'), or +[`ThermocyclerContext`](#opentrons.protocol_api.ThermocyclerContext 'opentrons.protocol_api.ThermocyclerContext'), +depending on what you requested with `module_name`. + +Changed in version 2\.13: Added `HeaterShakerContext` return value. + +Changed in version 2\.15: Added `MagneticBlockContext` return value. + +New in version 2\.0\. + +load*trash_bin(\_self*, _location: 'DeckLocation'_) → 'TrashBin' +Load a trash bin on the deck of a Flex. + +See [Trash Bin](index.html#configure-trash-bin) for details. + +If you try to load a trash bin on an OT\-2, the API will raise an error. + +Parameters: +**location** – The [deck slot](index.html#deck-slots) where the trash bin is. The +location can be any unoccupied slot in column 1 or 3\. + +If you try to load a trash bin in column 2 or 4, the API will raise an error. + +New in version 2\.16\. + +load*waste_chute(\_self*) → 'WasteChute' +Load the waste chute on the deck of a Flex. + +See [Waste Chute](index.html#configure-waste-chute) for details, including the deck configuration +variants of the waste chute. + +The deck plate adapter for the waste chute can only go in slot D3\. If you try to +load another item in slot D3 after loading the waste chute, or vice versa, the +API will raise an error. + +New in version 2\.16\. + +_property_ loaded_instruments*: [Dict](https://docs.python.org/3/library/typing.html#typing.Dict '(in Python v3.12)')\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)'), [InstrumentContext](index.html#opentrons.protocol_api.InstrumentContext 'opentrons.protocol_api.instrument_context.InstrumentContext')]* +Get the instruments that have been loaded into the protocol. + +This is a map of mount name to instruments previously loaded with +[`load_instrument()`](#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument'). It does not reflect what instruments are actually +installed on the robot. For example, if the robot has instruments installed on +both mounts but your protocol has only loaded one of them with +[`load_instrument()`](#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument'), the unused one will not be included in +`loaded_instruments`. + +Returns: +A dict mapping mount name (`"left"` or `"right"`) to the +instrument in that mount. If a mount has no loaded instrument, that key +will be missing from the dict. + +New in version 2\.0\. + +_property_ loaded_labwares*: [Dict](https://docs.python.org/3/library/typing.html#typing.Dict '(in Python v3.12)')\[[int](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)'), [Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')]* +Get the labwares that have been loaded into the protocol context. + +Slots with nothing in them will not be present in the return value. + +Note + +If a module is present on the deck but no labware has been loaded +into it with `module.load_labware()`, there will +be no entry for that slot in this value. That means you should not +use `loaded_labwares` to determine if a slot is available or not, +only to get a list of labwares. If you want a data structure of all +objects on the deck regardless of type, use [`deck`](#opentrons.protocol_api.ProtocolContext.deck 'opentrons.protocol_api.ProtocolContext.deck'). + +Returns: +Dict mapping deck slot number to labware, sorted in order of +the locations. + +New in version 2\.0\. + +_property_ loaded_modules*: [Dict](https://docs.python.org/3/library/typing.html#typing.Dict '(in Python v3.12)')\[[int](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)'), [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[TemperatureModuleContext](index.html#opentrons.protocol_api.TemperatureModuleContext 'opentrons.protocol_api.module_contexts.TemperatureModuleContext'), [MagneticModuleContext](index.html#opentrons.protocol_api.MagneticModuleContext 'opentrons.protocol_api.module_contexts.MagneticModuleContext'), [ThermocyclerContext](index.html#opentrons.protocol_api.ThermocyclerContext 'opentrons.protocol_api.module_contexts.ThermocyclerContext'), [HeaterShakerContext](index.html#opentrons.protocol_api.HeaterShakerContext 'opentrons.protocol_api.module_contexts.HeaterShakerContext'), [MagneticBlockContext](index.html#opentrons.protocol_api.MagneticBlockContext 'opentrons.protocol_api.module_contexts.MagneticBlockContext'), AbsorbanceReaderContext]]* +Get the modules loaded into the protocol context. + +This is a map of deck positions to modules loaded by previous calls to +[`load_module()`](#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module'). It does not reflect what modules are actually attached +to the robot. For example, if the robot has a Magnetic Module and a Temperature +Module attached, but the protocol has only loaded the Temperature Module with +[`load_module()`](#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module'), only the Temperature Module will be included in +`loaded_modules`. + +Returns: +Dict mapping slot name to module contexts. The elements may not be +ordered by slot number. + +New in version 2\.0\. + +_property_ max_speeds*: AxisMaxSpeeds* +Per\-axis speed limits for moving instruments. + +Changing values within this property sets the speed limit for each non\-plunger +axis of the robot. Note that this property only sets upper limits and can’t +exceed the physical speed limits of the movement system. + +This property is a dict mapping string names of axes to float values +of maximum speeds in mm/s. To change a speed, set that axis’s value. To +reset an axis’s speed to default, delete the entry for that axis +or assign it to `None`. + +See [Axis Speed Limits](index.html#axis-speed-limits) for examples. + +Note + +This property is not yet supported in API version 2\.14 or higher. + +New in version 2\.0\. + +move*labware(\_self*, _labware: 'Labware'_, _new_location: 'Union\[DeckLocation, Labware, ModuleTypes, OffDeckType, WasteChute]'_, _use_gripper: 'bool' \= False_, _pick_up_offset: 'Optional\[Mapping\[str, float]]' \= None_, _drop_offset: 'Optional\[Mapping\[str, float]]' \= None_) → 'None' +Move a loaded labware to a new location. + +See [Moving Labware](index.html#moving-labware) for more details. + +Parameters: + +- **labware** – The labware to move. It should be a labware already loaded + using [`load_labware()`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware'). +- **new_location** – Where to move the labware to. This is either: + + - A deck slot like `1`, `"1"`, or `"D1"`. See [Deck Slots](index.html#deck-slots). + - A hardware module that’s already been loaded on the deck + with [`load_module()`](#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module'). + - A labware or adapter that’s already been loaded on the deck + with [`load_labware()`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') or [`load_adapter()`](#opentrons.protocol_api.ProtocolContext.load_adapter 'opentrons.protocol_api.ProtocolContext.load_adapter'). + - The special constant [`OFF_DECK`](#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK'). + +- **use_gripper** – Whether to use the Flex Gripper for this movement. + + - If `True`, use the gripper to perform an automatic + movement. This will raise an error in an OT\-2 protocol. + - If `False`, pause protocol execution until the user + performs the movement. Protocol execution remains paused until + the user presses **Confirm and resume**. + +Gripper\-only parameters: + +Parameters: + +- **pick_up_offset** – Optional x, y, z vector offset to use when picking up labware. +- **drop_offset** – Optional x, y, z vector offset to use when dropping off labware. + +Before moving a labware to or from a hardware module, make sure that the labware’s +current and new locations are accessible, i.e., open the Thermocycler lid or +open the Heater\-Shaker’s labware latch. + +New in version 2\.15\. + +_property_ params*: Parameters* +The values of runtime parameters, as set during run setup. + +Each attribute of this object corresponds to the `variable_name` of a parameter. +See [Using Parameters](index.html#using-rtp) for details. + +Parameter values can only be set during run setup. If you try to alter the value +of any attribute of `params`, the API will raise an error. + +New in version 2\.18\. + +pause(_self_, _msg: 'Optional\[str]' \= None_) → 'None' +Pause execution of the protocol until it’s resumed. + +A human can resume the protocol in the Opentrons App or on the touchscreen. + +Note + +In Python Protocol API version 2\.13 and earlier, the pause will only +take effect on the next function call that involves moving the robot. + +Parameters: +**msg** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional message to show in the run log entry for the pause step. + +New in version 2\.0\. + +_property_ rail_lights_on*: [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')* +Returns `True` if the robot’s ambient lighting is on. + +New in version 2\.5\. + +resume(_self_) → 'None' +Resume the protocol after [`pause()`](#opentrons.protocol_api.ProtocolContext.pause 'opentrons.protocol_api.ProtocolContext.pause'). + +Deprecated since version 2\.12: The Python Protocol API supports no safe way for a protocol to resume itself. +If you’re looking for a way for your protocol to resume automatically +after a period of time, use [`delay()`](#opentrons.protocol_api.ProtocolContext.delay 'opentrons.protocol_api.ProtocolContext.delay'). + +New in version 2\.0\. + +set*rail_lights(\_self*, _on: 'bool'_) → 'None' +Controls the robot’s ambient lighting (rail lights). + +Parameters: +**on** ([_bool_](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')) – If `True`, turn on the lights; otherwise, turn them off. + +New in version 2\.5\. + +### Instruments + +_class_ opentrons.protocol*api.InstrumentContext(\_core: AbstractInstrument\[AbstractWellCore]*, _protocol_core: AbstractProtocol\[AbstractInstrument\[AbstractWellCore], AbstractLabware\[AbstractWellCore], AbstractModuleCore]_, _broker: LegacyBroker_, _api_version: APIVersion_, _tip_racks: [List](https://docs.python.org/3/library/typing.html#typing.List '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')]_, _trash: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware'), [TrashBin](index.html#opentrons.protocol_api.TrashBin 'opentrons.protocol_api.disposal_locations.TrashBin'), [WasteChute](index.html#opentrons.protocol_api.WasteChute 'opentrons.protocol_api.disposal_locations.WasteChute')]]_, _requested_as: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')_) +A context for a specific pipette or instrument. + +The InstrumentContext class provides the objects, attributes, and methods that allow +you to use pipettes in your protocols. + +Methods generally fall into one of two categories. + +> - They can change the state of the InstrumentContext object, like how fast it +> moves liquid or where it disposes of used tips. +> - They can command the instrument to perform an action, like picking up tips, +> moving to certain locations, and aspirating or dispensing liquid. + +Objects in this class should not be instantiated directly. Instead, instances are +returned by [`ProtocolContext.load_instrument()`](#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument'). + +New in version 2\.0\. + +_property_ active_channels*: [int](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)')* +The number of channels the pipette will use to pick up tips. + +By default, all channels on the pipette. Use [`configure_nozzle_layout()`](#opentrons.protocol_api.InstrumentContext.configure_nozzle_layout 'opentrons.protocol_api.InstrumentContext.configure_nozzle_layout') +to set the pipette to use fewer channels. + +New in version 2\.16\. + +air*gap(\_self*, _volume: 'Optional\[float]' \= None_, _height: 'Optional\[float]' \= None_) → 'InstrumentContext' +Draw air into the pipette’s tip at the current well. + +See [Air Gap](index.html#air-gap). + +Parameters: + +- **volume** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The amount of air, measured in µL. Calling `air_gap()` with no + arguments uses the entire remaining volume in the pipette. +- **height** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The height, in mm, to move above the current well before creating + the air gap. The default is 5 mm above the current well. + +Raises: +`UnexpectedTipRemovalError` – If no tip is attached to the pipette. + +Raises: +[**RuntimeError**](https://docs.python.org/3/library/exceptions.html#RuntimeError '(in Python v3.12)') – If location cache is `None`. This should happen if +`air_gap()` is called without first calling a method +that takes a location (e.g., [`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate'), +[`dispense()`](#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense')) + +Returns: +This instance. + +Note + +Both `volume` and `height` are optional, but if you want to specify only +`height` you must do it as a keyword argument: +`pipette.air_gap(height=2)`. If you call `air_gap` with a single, +unnamed argument, it will always be interpreted as a volume. + +New in version 2\.0\. + +_property_ api_version*: APIVersion* + +New in version 2\.0\. + +aspirate(_self_, _volume: 'Optional\[float]' \= None_, _location: 'Optional\[Union\[types.Location, labware.Well]]' \= None_, _rate: 'float' \= 1\.0_) → 'InstrumentContext' +Draw liquid into a pipette tip. + +See [Aspirate](index.html#new-aspirate) for more details and examples. + +Parameters: + +- **volume** ([_int_](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)') _or_ [_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The volume to aspirate, measured in µL. If unspecified, + defaults to the maximum volume for the pipette and its currently + attached tip. + +If `aspirate` is called with a volume of precisely 0, its behavior +depends on the API level of the protocol. On API levels below 2\.16, +it will behave the same as a volume of `None`/unspecified: aspirate +until the pipette is full. On API levels at or above 2\.16, no liquid +will be aspirated. + +- **location** – Tells the robot where to aspirate from. The location can be + a [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') or a [`Location`](#opentrons.types.Location 'opentrons.types.Location'). + +> - If the location is a `Well`, the robot will aspirate at +> or above the bottom center of the well. The distance (in mm) +> from the well bottom is specified by +> [`well_bottom_clearance.aspirate`](#opentrons.protocol_api.InstrumentContext.well_bottom_clearance 'opentrons.protocol_api.InstrumentContext.well_bottom_clearance'). +> - If the location is a `Location` (e.g., the result of +> [`Well.top()`](#opentrons.protocol_api.Well.top 'opentrons.protocol_api.Well.top') or [`Well.bottom()`](#opentrons.protocol_api.Well.bottom 'opentrons.protocol_api.Well.bottom')), the robot +> will aspirate from that specified position. +> - If the `location` is unspecified, the robot will +> aspirate from its current position. + +- **rate** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – A multiplier for the default flow rate of the pipette. Calculated + as `rate` multiplied by [`flow_rate.aspirate`](#opentrons.protocol_api.InstrumentContext.flow_rate 'opentrons.protocol_api.InstrumentContext.flow_rate'). If not specified, defaults to 1\.0\. See + [Pipette Flow Rates](index.html#new-plunger-flow-rates). + +Returns: +This instance. + +Note + +If `aspirate` is called with a single, unnamed argument, it will treat +that argument as `volume`. If you want to call `aspirate` with only +`location`, specify it as a keyword argument: +`pipette.aspirate(location=plate['A1'])` + +New in version 2\.0\. + +blow*out(\_self*, _location: 'Optional\[Union\[types.Location, labware.Well, TrashBin, WasteChute]]' \= None_) → 'InstrumentContext' +Blow an extra amount of air through a pipette’s tip to clear it. + +If [`dispense()`](#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') is used to empty a pipette, usually a small amount of +liquid remains in the tip. During a blowout, the pipette moves the plunger +beyond its normal limits to help remove all liquid from the pipette tip. See +[Blow Out](index.html#blow-out). + +Parameters: +**location** ([`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') or [`Location`](#opentrons.types.Location 'opentrons.types.Location') or `None`) – The blowout location. If no location is specified, the pipette +will blow out from its current position. + +Changed in version 2\.16: Accepts `TrashBin` and `WasteChute` values. + +Raises: +[**RuntimeError**](https://docs.python.org/3/library/exceptions.html#RuntimeError '(in Python v3.12)') – If no location is specified and the location cache is +`None`. This should happen if `blow_out()` is called +without first calling a method that takes a location, like +[`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') or [`dispense()`](#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense'). + +Returns: +This instance. + +New in version 2\.0\. + +_property_ channels*: [int](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)')* +The number of channels on the pipette. + +Possible values are 1, 8, or 96\. + +See also [`type`](#opentrons.protocol_api.InstrumentContext.type 'opentrons.protocol_api.InstrumentContext.type'). + +New in version 2\.0\. + +configure*for_volume(\_self*, _volume: 'float'_) → 'None' +Configure a pipette to handle a specific volume of liquid, measured in µL. +The pipette enters a volume mode depending on the volume provided. Changing +pipette modes alters properties of the instance of +[`InstrumentContext`](#opentrons.protocol_api.InstrumentContext 'opentrons.protocol_api.InstrumentContext'), such as default flow rate, minimum volume, and +maximum volume. The pipette remains in the mode set by this function until it is +called again. + +The Flex 1\-Channel 50 µL and Flex 8\-Channel 50 µL pipettes must operate in a +low\-volume mode to accurately dispense very small volumes of liquid. Low\-volume +mode can only be set by calling `configure_for_volume()`. See +[Volume Modes](index.html#pipette-volume-modes). + +Note + +Changing a pipette’s mode will reset its [flow rates](index.html#new-plunger-flow-rates). + +This function will raise an error if called when the pipette’s tip contains +liquid. It won’t raise an error if a tip is not attached, but changing modes may +affect which tips the pipette can subsequently pick up without raising an error. + +This function will also raise an error if `volume` is outside of the +[minimum and maximum capacities](index.html#new-pipette-models) of the pipette (e.g., +setting `volume=1` for a Flex 1000 µL pipette). + +Parameters: +**volume** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The volume, in µL, that the pipette will prepare to handle. + +New in version 2\.15\. + +configure*nozzle_layout(\_self*, _style: 'NozzleLayout'_, _start: 'Optional\[str]' \= None_, _end: 'Optional\[str]' \= None_, _front_right: 'Optional\[str]' \= None_, _back_left: 'Optional\[str]' \= None_, _tip_racks: 'Optional\[List\[labware.Labware]]' \= None_) → 'None' +Configure how many tips the 8\-channel or 96\-channel pipette will pick up. + +Changing the nozzle layout will affect gantry movement for all subsequent +pipetting actions that the pipette performs. It also alters the pipette’s +behavior for picking up tips. The pipette will continue to use the specified +layout until this function is called again. + +Note + +When picking up fewer than 96 tips at once, the tip rack _must not_ be +placed in a tip rack adapter in the deck. If you try to pick up fewer than 96 +tips from a tip rack that is in an adapter, the API will raise an error. + +Parameters: + +- **style** (`NozzleLayout` or `None`) – The shape of the nozzle layout. + + - `SINGLE` sets the pipette to use 1 nozzle. This corresponds to a single of well on labware. + - `COLUMN` sets the pipette to use 8 nozzles, aligned from front to back + with respect to the deck. This corresponds to a column of wells on labware. + - `PARTIAL_COLUMN` sets the pipette to use 2\-7 nozzles, aligned from front to back + with respect to the deck. + - `ROW` sets the pipette to use 12 nozzles, aligned from left to right + with respect to the deck. This corresponds to a row of wells on labware. + - `ALL` resets the pipette to use all of its nozzles. Calling + `configure_nozzle_layout` with no arguments also resets the pipette. + +- **start** (str or `None`) – The primary nozzle of the layout, which the robot uses + to determine how it will move to different locations on the deck. The string + should be of the same format used when identifying wells by name. + Required unless setting `style=ALL`. + +Note + +If possible, don’t use both `start="A1"` and `start="A12"` to pick up +tips _from the same rack_. Doing so can affect positional accuracy. + +- **end** (str or `None`) – The nozzle at the end of a linear layout, which is used + to determine how many tips will be picked up by a pipette. The string + should be of the same format used when identifying wells by name. + Required when setting `style=PARTIAL_COLUMN`. + +Note + +Nozzle layouts numbering between 2\-7 nozzles, account for the distance from +`start`. For example, 4 nozzles would require `start="H1"` and `end="E1"`. + +- **tip_racks** (List\[[`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware')]) – Behaves the same as setting the `tip_racks` parameter of + [`load_instrument()`](#opentrons.protocol_api.ProtocolContext.load_instrument 'opentrons.protocol_api.ProtocolContext.load_instrument'). If not specified, the new configuration resets + [`InstrumentContext.tip_racks`](#opentrons.protocol_api.InstrumentContext.tip_racks 'opentrons.protocol_api.InstrumentContext.tip_racks') and you must specify the location + every time you call [`pick_up_tip()`](#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip'). + +New in version 2\.16\. + +consolidate(_self_, _volume: 'Union\[float_, _Sequence\[float]]'_, _source: 'List\[labware.Well]'_, _dest: 'labware.Well'_, _\\\*args: 'Any'_, _\\\*\\\*kwargs: 'Any'_) → 'InstrumentContext' +Move liquid from multiple source wells to a single destination well. + +Parameters: + +- **volume** – The amount, in µL, to aspirate from each source well. +- **source** – A list of wells to aspirate liquid from. +- **dest** – A single well to dispense liquid into. +- **kwargs** – See [`transfer()`](#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') and the [Complex Liquid Handling Parameters](index.html#complex-params) page. + Some parameters behave differently than when transferring. + `disposal_volume` and `mix_before` are ignored. + +Returns: +This instance. + +New in version 2\.0\. + +_property_ current_volume*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The current amount of liquid held in the pipette, measured in µL. + +New in version 2\.0\. + +_property_ default_speed*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The speed at which the robot’s gantry moves in mm/s. + +The default speed for Flex varies between 300 and 350 mm/s. The OT\-2 default is +400 mm/s. In addition to changing the default, the speed of individual motions +can be changed with the `speed` argument of the +[`InstrumentContext.move_to()`](#opentrons.protocol_api.InstrumentContext.move_to 'opentrons.protocol_api.InstrumentContext.move_to') method. See [Gantry Speed](index.html#gantry-speed). + +New in version 2\.0\. + +detect*liquid_presence(\_self*, _well: 'labware.Well'_) → 'bool' +Check if there is liquid in a well. + +Returns: +A boolean. + +New in version 2\.20\. + +dispense(_self_, _volume: 'Optional\[float]' \= None_, _location: 'Optional\[Union\[types.Location, labware.Well, TrashBin, WasteChute]]' \= None_, _rate: 'float' \= 1\.0_, _push_out: 'Optional\[float]' \= None_) → 'InstrumentContext' +Dispense liquid from a pipette tip. + +See [Dispense](index.html#new-dispense) for more details and examples. + +Parameters: + +- **volume** ([_int_](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)') _or_ [_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The volume to dispense, measured in µL. + + - If unspecified or `None`, dispense the [`current_volume`](#opentrons.protocol_api.InstrumentContext.current_volume 'opentrons.protocol_api.InstrumentContext.current_volume'). + - If 0, the behavior of `dispense()` depends on the API level + of the protocol. In API version 2\.16 and earlier, dispense all + liquid in the pipette (same as unspecified or `None`). In API + version 2\.17 and later, dispense no liquid. + - If greater than [`current_volume`](#opentrons.protocol_api.InstrumentContext.current_volume 'opentrons.protocol_api.InstrumentContext.current_volume'), the behavior of + `dispense()` depends on the API level of the protocol. In API + version 2\.16 and earlier, dispense all liquid in the pipette. + In API version 2\.17 and later, raise an error. + +- **location** – Tells the robot where to dispense liquid held in the pipette. + The location can be a [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well'), [`Location`](#opentrons.types.Location 'opentrons.types.Location'), + [`TrashBin`](#opentrons.protocol_api.TrashBin 'opentrons.protocol_api.TrashBin'), or [`WasteChute`](#opentrons.protocol_api.WasteChute 'opentrons.protocol_api.WasteChute'). + +> - If a `Well`, the pipette will dispense +> at or above the bottom center of the well. The distance (in +> mm) from the well bottom is specified by +> [`well_bottom_clearance.dispense`](#opentrons.protocol_api.InstrumentContext.well_bottom_clearance 'opentrons.protocol_api.InstrumentContext.well_bottom_clearance'). + If a `Location` (e.g., the result of +> [`Well.top()`](#opentrons.protocol_api.Well.top 'opentrons.protocol_api.Well.top') or [`Well.bottom()`](#opentrons.protocol_api.Well.bottom 'opentrons.protocol_api.Well.bottom')), the pipette +> will dispense at that specified position. + If a trash container, the pipette will dispense at a location +> relative to its center and the trash container’s top center. +> See [Position Relative to Trash Containers](index.html#position-relative-trash) for details. + If unspecified, the pipette will +> dispense at its current position. +> If only a `location` is passed (e.g., +> `pipette.dispense(location=plate['A1'])`), all of the +> liquid aspirated into the pipette will be dispensed (the +> amount is accessible through [`current_volume`](#opentrons.protocol_api.InstrumentContext.current_volume 'opentrons.protocol_api.InstrumentContext.current_volume')). + +Changed in version 2\.16: Accepts `TrashBin` and `WasteChute` values. + +- **rate** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – How quickly a pipette dispenses liquid. The speed in µL/s is + calculated as `rate` multiplied by [`flow_rate.dispense`](#opentrons.protocol_api.InstrumentContext.flow_rate 'opentrons.protocol_api.InstrumentContext.flow_rate'). If not specified, defaults to 1\.0\. See + [Pipette Flow Rates](index.html#new-plunger-flow-rates). +- **push_out** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – Continue past the plunger bottom to help ensure all liquid + leaves the tip. Measured in µL. The default value is `None`. + +See [Push Out After Dispense](index.html#push-out-dispense) for details. + +Returns: +This instance. + +Note + +If `dispense` is called with a single, unnamed argument, it will treat +that argument as `volume`. If you want to call `dispense` with only +`location`, specify it as a keyword argument: +`pipette.dispense(location=plate['A1'])`. + +Changed in version 2\.15: Added the `push_out` parameter. + +Changed in version 2\.17: Behavior of the `volume` parameter. + +New in version 2\.0\. + +distribute(_self_, _volume: 'Union\[float_, _Sequence\[float]]'_, _source: 'labware.Well'_, _dest: 'List\[labware.Well]'_, _\\\*args: 'Any'_, _\\\*\\\*kwargs: 'Any'_) → 'InstrumentContext' +Move a volume of liquid from one source to multiple destinations. + +Parameters: + +- **volume** – The amount, in µL, to dispense into each destination well. +- **source** – A single well to aspirate liquid from. +- **dest** – A list of wells to dispense liquid into. +- **kwargs** – See [`transfer()`](#opentrons.protocol_api.InstrumentContext.transfer 'opentrons.protocol_api.InstrumentContext.transfer') and the [Complex Liquid Handling Parameters](index.html#complex-params) page. + Some parameters behave differently than when transferring. + +> - `disposal_volume` aspirates additional liquid to improve the accuracy +> of each dispense. Defaults to the minimum volume of the pipette. See +> [Disposal Volume](index.html#param-disposal-volume) for details. +> - `mix_after` is ignored. + +Returns: +This instance. + +New in version 2\.0\. + +drop*tip(\_self*, _location: 'Optional\[Union\[types.Location, labware.Well, TrashBin, WasteChute]]' \= None_, _home_after: 'Optional\[bool]' \= None_) → 'InstrumentContext' +Drop the current tip. + +See [Dropping a Tip](index.html#pipette-drop-tip) for examples. + +If no location is passed (e.g. `pipette.drop_tip()`), the pipette will drop +the attached tip into its [`trash_container`](#opentrons.protocol_api.InstrumentContext.trash_container 'opentrons.protocol_api.InstrumentContext.trash_container'). + +The location in which to drop the tip can be manually specified with the +`location` argument. The `location` argument can be specified in several +ways: + +> - As a [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well'). This uses a default location relative to the well. +> This style of call can be used to make the robot drop a tip into labware +> like a well plate or a reservoir. For example, +> `pipette.drop_tip(location=reservoir["A1"])`. +> - As a [`Location`](#opentrons.types.Location 'opentrons.types.Location'). For example, to drop a tip from an +> unusually large height above the tip rack, you could call +> `pipette.drop_tip(tip_rack["A1"].top(z=10))`. +> - As a [`TrashBin`](#opentrons.protocol_api.TrashBin 'opentrons.protocol_api.TrashBin'). This uses a default location relative to the +> `TrashBin` object. For example, +> `pipette.drop_tip(location=trash_bin)`. +> - As a [`WasteChute`](#opentrons.protocol_api.WasteChute 'opentrons.protocol_api.WasteChute'). This uses a default location relative to +> the `WasteChute` object. For example, +> `pipette.drop_tip(location=waste_chute)`. + +In API versions 2\.15 to 2\.17, if `location` is a `TrashBin` or not +specified, the API will instruct the pipette to drop tips in different locations +within the bin. Varying the tip drop location helps prevent tips +from piling up in a single location. + +Starting with API version 2\.18, the API will only vary the tip drop location if +`location` is not specified. Specifying a `TrashBin` as the `location` +behaves the same as specifying [`TrashBin.top()`](#opentrons.protocol_api.TrashBin.top 'opentrons.protocol_api.TrashBin.top'), which is a fixed position. + +Parameters: + +- **location** ([`Location`](#opentrons.types.Location 'opentrons.types.Location') or [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') or `None`) – Where to drop the tip. + +Changed in version 2\.16: Accepts `TrashBin` and `WasteChute` values. + +- **home_after** – Whether to home the pipette’s plunger after dropping the tip. If not + specified, defaults to `True` on an OT\-2\. + +When `False`, the pipette does not home its plunger. This can save a few +seconds, but is not recommended. Homing helps the robot track the pipette’s +position. + +Returns: +This instance. + +New in version 2\.0\. + +_property_ flow_rate*: FlowRates* +The speeds, in µL/s, configured for the pipette. + +See [Pipette Flow Rates](index.html#new-plunger-flow-rates). + +This is an object with attributes `aspirate`, `dispense`, and `blow_out` +holding the flow rate for the corresponding operation. + +Note + +Setting values of [`speed`](#opentrons.protocol_api.InstrumentContext.speed 'opentrons.protocol_api.InstrumentContext.speed'), which is deprecated, will override the +values in [`flow_rate`](#opentrons.protocol_api.InstrumentContext.flow_rate 'opentrons.protocol_api.InstrumentContext.flow_rate'). + +New in version 2\.0\. + +_property_ has_tip*: [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')* +Whether this instrument has a tip attached or not. + +The value of this property is determined logically by the API, not by detecting +the physical presence of a tip. This is the case even on Flex, which has sensors +to detect tip attachment. + +New in version 2\.7\. + +home(_self_) → 'InstrumentContext' +Home the robot. + +See [Homing](index.html#utility-homing). + +Returns: +This instance. + +New in version 2\.0\. + +home*plunger(\_self*) → 'InstrumentContext' +Home the plunger associated with this mount. + +Returns: +This instance. + +New in version 2\.0\. + +_property_ hw_pipette*: PipetteDict* +View the information returned by the hardware API directly. + +Raises: +[`types.PipetteNotAttachedError`](#opentrons.types.PipetteNotAttachedError 'opentrons.types.PipetteNotAttachedError') if the pipette is +no longer attached (should not happen). + +New in version 2\.0\. + +_property_ liquid_presence_detection*: [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')* +Gets the global setting for liquid level detection. + +When True, liquid_probe will be called before +aspirates and dispenses to bring the tip to the liquid level. + +The default value is False. + +New in version 2\.20\. + +_property_ max_volume*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The maximum volume, in µL, that the pipette can hold. + +The maximum volume that you can actually aspirate might be lower than this, +depending on what kind of tip is attached to this pipette. For example, a P300 +Single\-Channel pipette always has a `max_volume` of 300 µL, but if it’s using +a 200 µL filter tip, its usable volume would be limited to 200 µL. + +New in version 2\.0\. + +_property_ min_volume*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The minimum volume, in µL, that the pipette can hold. This value may change +based on the [volume mode](index.html#pipette-volume-modes) that the pipette is +currently configured for. + +New in version 2\.0\. + +mix(_self_, _repetitions: 'int' \= 1_, _volume: 'Optional\[float]' \= None_, _location: 'Optional\[Union\[types.Location, labware.Well]]' \= None_, _rate: 'float' \= 1\.0_) → 'InstrumentContext' +Mix a volume of liquid by repeatedly aspirating and dispensing it in a single location. + +See [Mix](index.html#mix) for examples. + +Parameters: + +- **repetitions** – Number of times to mix (default is 1\). +- **volume** – The volume to mix, measured in µL. If unspecified, defaults + to the maximum volume for the pipette and its attached tip. + +If `mix` is called with a volume of precisely 0, its behavior +depends on the API level of the protocol. On API levels below 2\.16, +it will behave the same as a volume of `None`/unspecified: mix +the full working volume of the pipette. On API levels at or above 2\.16, +no liquid will be mixed. + +- **location** – The [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') or [`Location`](#opentrons.types.Location 'opentrons.types.Location') where the + pipette will mix. If unspecified, the pipette will mix at its + current position. +- **rate** – How quickly the pipette aspirates and dispenses liquid while + mixing. The aspiration flow rate is calculated as `rate` + multiplied by [`flow_rate.aspirate`](#opentrons.protocol_api.InstrumentContext.flow_rate 'opentrons.protocol_api.InstrumentContext.flow_rate'). The + dispensing flow rate is calculated as `rate` multiplied by + [`flow_rate.dispense`](#opentrons.protocol_api.InstrumentContext.flow_rate 'opentrons.protocol_api.InstrumentContext.flow_rate'). See + [Pipette Flow Rates](index.html#new-plunger-flow-rates). + +Raises: +`UnexpectedTipRemovalError` – If no tip is attached to the pipette. + +Returns: +This instance. + +Note + +All the arguments of `mix` are optional. However, if you omit one of them, +all subsequent arguments must be passed as keyword arguments. For instance, +`pipette.mix(1, location=wellplate['A1'])` is a valid call, but +`pipette.mix(1, wellplate['A1'])` is not. + +New in version 2\.0\. + +_property_ model*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The model string for the pipette (e.g., `'p300_single_v1.3'`) + +New in version 2\.0\. + +_property_ mount*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +Return the name of the mount the pipette is attached to. + +The possible names are `"left"` and `"right"`. + +New in version 2\.0\. + +move*to(\_self*, _location: 'Union\[types.Location, TrashBin, WasteChute]'_, _force_direct: 'bool' \= False_, _minimum_z_height: 'Optional\[float]' \= None_, _speed: 'Optional\[float]' \= None_, _publish: 'bool' \= True_) → 'InstrumentContext' +Move the instrument. + +See [Move To](index.html#move-to) for examples. + +Parameters: + +- **location** ([`Location`](#opentrons.types.Location 'opentrons.types.Location')) – Where to move to. + +Changed in version 2\.16: Accepts `TrashBin` and `WasteChute` values. + +- **force_direct** – If `True`, move directly to the destination without arc + motion. + +Warning + +Forcing direct motion can cause the pipette to crash +into labware, modules, or other objects on the deck. + +- **minimum_z_height** – An amount, measured in mm, to raise the mid\-arc height. + The mid\-arc height can’t be lowered. +- **speed** – The speed at which to move. By default, + [`InstrumentContext.default_speed`](#opentrons.protocol_api.InstrumentContext.default_speed 'opentrons.protocol_api.InstrumentContext.default_speed'). This controls the + straight linear speed of the motion. To limit individual axis + speeds, use [`ProtocolContext.max_speeds`](#opentrons.protocol_api.ProtocolContext.max_speeds 'opentrons.protocol_api.ProtocolContext.max_speeds'). +- **publish** – Whether to list this function call in the run preview. + Default is `True`. + +New in version 2\.0\. + +_property_ name*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The name string for the pipette (e.g., `"p300_single"`). + +New in version 2\.0\. + +pick*up_tip(\_self*, _location: 'Union\[types.Location, labware.Well, labware.Labware, None]' \= None_, _presses: 'Optional\[int]' \= None_, _increment: 'Optional\[float]' \= None_, _prep_after: 'Optional\[bool]' \= None_) → 'InstrumentContext' +Pick up a tip for the pipette to run liquid\-handling commands. + +See [Picking Up a Tip](index.html#basic-tip-pickup). + +If no location is passed, the pipette will pick up the next available tip in its +[`tip_racks`](#opentrons.protocol_api.InstrumentContext.tip_racks 'opentrons.protocol_api.InstrumentContext.tip_racks') list. Within each tip rack, tips will +be picked up in the order specified by the labware definition and +[`Labware.wells()`](#opentrons.protocol_api.Labware.wells 'opentrons.protocol_api.Labware.wells'). To adjust where the sequence starts, use +[`starting_tip`](#opentrons.protocol_api.InstrumentContext.starting_tip 'opentrons.protocol_api.InstrumentContext.starting_tip'). + +The exact position for tip pickup accounts for the length of the tip and how +much the tip overlaps with the pipette nozzle. These measurements are fixed +values on Flex, and are based on the results of tip length calibration on OT\-2\. + +Note + +API version 2\.19 updates the tip overlap values for Flex. When updating a +protocol from 2\.18 (or lower) to 2\.19 (or higher), pipette performance +should improve without additional changes to your protocol. Nevertheless, it +is good practice after updating to do the following: + +- Run Labware Position Check. +- Perform a dry run of your protocol. +- If tip position is slightly higher than expected, adjust the `location` + parameter of pipetting actions to achieve the desired result. + +Parameters: + +- **location** ([`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') or [`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') or [`types.Location`](#opentrons.types.Location 'opentrons.types.Location')) – The location from which to pick up a tip. The `location` + argument can be specified in several ways: + +> - As a [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well'). For example, +> `pipette.pick_up_tip(tiprack.wells()[0])` will always pick +> up the first tip in `tiprack`, even if the rack is not a +> member of [`InstrumentContext.tip_racks`](#opentrons.protocol_api.InstrumentContext.tip_racks 'opentrons.protocol_api.InstrumentContext.tip_racks'). +> - As a labware. `pipette.pick_up_tip(tiprack)` will pick up +> the next available tip in `tiprack`, even if the rack is +> not a member of [`InstrumentContext.tip_racks`](#opentrons.protocol_api.InstrumentContext.tip_racks 'opentrons.protocol_api.InstrumentContext.tip_racks'). +> - As a [`Location`](#opentrons.types.Location 'opentrons.types.Location'). Use this to make fine +> adjustments to the pickup location. For example, to tell +> the robot to start its pick up tip routine 1 mm closer to +> the top of the well in the tip rack, call +> `pipette.pick_up_tip(tiprack["A1"].top(z=-1))`. + +- **presses** ([_int_](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)')) – The number of times to lower and then raise the pipette when + picking up a tip, to ensure a good seal. Zero (`0`) will + result in the pipette hovering over the tip but not picking it + up (generally not desirable, but could be used for a dry run). + +> Deprecated since version 2\.14: Use the Opentrons App to change pipette pick\-up settings. + +- **increment** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The additional distance to travel on each successive press. + For example, if `presses=3` and `increment=1.0`, then the + first press will travel down into the tip by 3\.5 mm, the + second by 4\.5 mm, and the third by 5\.5 mm). + +> Deprecated since version 2\.14: Use the Opentrons App to change pipette pick\-up settings. + +- **prep_after** ([_bool_](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')) – Whether the pipette plunger should prepare itself to aspirate + immediately after picking up a tip. + +If `True`, the pipette will move its plunger position to +bottom in preparation for any following calls to +[`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate'). + +If `False`, the pipette will prepare its plunger later, +during the next call to [`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate'). This is +accomplished by moving the tip to the top of the well, and +positioning the plunger outside any potential liquids. + +Warning + +This is provided for compatibility with older Python +Protocol API behavior. You should normally leave this +unset. + +Setting `prep_after=False` may create an unintended +pipette movement, when the pipette automatically moves +the tip to the top of the well to prepare the plunger. + +Changed in version 2\.13: Adds the `prep_after` argument. In version 2\.12 and earlier, the plunger +can’t prepare itself for aspiration during [`pick_up_tip()`](#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip'), and will +instead always prepare during [`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate'). Version 2\.12 and earlier +will raise an `APIVersionError` if a value is set for `prep_after`. + +Changed in version 2\.19: Uses new values for how much a tip overlaps with the pipette nozzle. + +Returns: +This instance. + +New in version 2\.0\. + +prepare*to_aspirate(\_self*) → 'None' +Prepare a pipette for aspiration. + +Before a pipette can aspirate into an empty tip, the plunger must be in its +bottom position. After dropping a tip or blowing out, the plunger will be in a +different position. This function moves the plunger to the bottom position, +regardless of its current position, to make sure that the pipette is ready to +aspirate. + +You rarely need to call this function. The API automatically prepares the +pipette for aspiration as part of other commands: + +> - After picking up a tip with [`pick_up_tip()`](#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip'). +> - When calling [`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate'), if the pipette isn’t already prepared. +> If the pipette is in a well, it will move out of the well, move the plunger, +> and then move back. + +Use `prepare_to_aspirate` when you need to control exactly when the plunger +motion will happen. A common use case is a pre\-wetting routine, which requires +preparing for aspiration, moving into a well, and then aspirating _without +leaving the well_: + +``` +pipette.move_to(well.bottom(z=2)) +pipette.delay(5) +pipette.mix(10, 10) +pipette.move_to(well.top(z=5)) +pipette.blow_out() +pipette.prepare_to_aspirate() +pipette.move_to(well.bottom(z=2)) +pipette.delay(5) +pipette.aspirate(10, well.bottom(z=2)) + +``` + +The call to `prepare_to_aspirate()` means that the plunger will be in the +bottom position before the call to `aspirate()`. Since it doesn’t need to +prepare again, it will not move up out of the well to move the plunger. It will +aspirate in place. + +New in version 2\.16\. + +require*liquid_presence(\_self*, _well: 'labware.Well'_) → 'None' +If there is no liquid in a well, raise an error. + +Returns: +None. + +New in version 2\.20\. + +reset*tipracks(\_self*) → 'None' +Reload all tips in each tip rack and reset the starting tip. + +New in version 2\.0\. + +_property_ return_height*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The height to return a tip to its tip rack. + +Returns: +A scaling factor to apply to the tip length. +During [`drop_tip()`](#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip'), this factor is multiplied by the tip +length to get the distance from the top of the well to drop the tip. + +New in version 2\.2\. + +return*tip(\_self*, _home_after: 'Optional\[bool]' \= None_) → 'InstrumentContext' +Drop the currently attached tip in its original location in the tip rack. + +Returning a tip does not reset tip tracking, so [`Well.has_tip`](#opentrons.protocol_api.Well.has_tip 'opentrons.protocol_api.Well.has_tip') will +remain `False` for the destination. + +Returns: +This instance. + +Parameters: +**home_after** – See the `home_after` parameter of [`drop_tip()`](#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip'). + +New in version 2\.0\. + +_property_ speed*: PlungerSpeeds* +The speeds (in mm/s) configured for the pipette plunger. + +This is an object with attributes `aspirate`, `dispense`, and `blow_out` +holding the plunger speeds for the corresponding operation. + +Note + +Setting values of [`flow_rate`](#opentrons.protocol_api.InstrumentContext.flow_rate 'opentrons.protocol_api.InstrumentContext.flow_rate') will override the values in +[`speed`](#opentrons.protocol_api.InstrumentContext.speed 'opentrons.protocol_api.InstrumentContext.speed'). + +Changed in version 2\.14: This property has been removed because it’s fundamentally misaligned with +the step\-wise nature of a pipette’s plunger speed configuration. Use +[`flow_rate`](#opentrons.protocol_api.InstrumentContext.flow_rate 'opentrons.protocol_api.InstrumentContext.flow_rate') instead. + +New in version 2\.0\. + +_property_ starting_tip*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[Well](index.html#opentrons.protocol_api.Well 'opentrons.protocol_api.labware.Well')]* +Which well of a tip rack the pipette should start at when automatically choosing tips to pick up. + +See [`pick_up_tip()`](#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip'). + +Note + +In robot software versions 6\.3\.0 and 6\.3\.1, protocols specifying API level +2\.14 ignored `starting_tip` on the second and subsequent calls to +[`InstrumentContext.pick_up_tip()`](#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') with no argument. This is fixed +for all API levels as of robot software version 7\.0\.0\. + +New in version 2\.0\. + +_property_ tip_racks*: [List](https://docs.python.org/3/library/typing.html#typing.List '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')]* +The tip racks that have been linked to this pipette. + +This is the property used to determine which tips to pick up next when calling +[`pick_up_tip()`](#opentrons.protocol_api.InstrumentContext.pick_up_tip 'opentrons.protocol_api.InstrumentContext.pick_up_tip') without arguments. See [Picking Up a Tip](index.html#basic-tip-pickup). + +New in version 2\.0\. + +touch*tip(\_self*, _location: 'Optional\[labware.Well]' \= None_, _radius: 'float' \= 1\.0_, _v_offset: 'float' \= \- 1\.0_, _speed: 'float' \= 60\.0_) → 'InstrumentContext' +Touch the pipette tip to the sides of a well, with the intent of removing leftover droplets. + +See [Touch Tip](index.html#touch-tip) for more details and examples. + +Parameters: + +- **location** ([`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') or `None`) – If no location is passed, the pipette will touch its tip at the + edges of the current well. +- **radius** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – How far to move, as a proportion of the target well’s radius. + When `radius=1.0`, the pipette tip will move all the way to the + edge of the target well. When `radius=0.5`, it will move to 50% + of the well’s radius. Default is 1\.0 (100%) +- **v_offset** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – How far above or below the well to touch the tip, measured in mm. + A positive offset moves the tip higher above the well. + A negative offset moves the tip lower into the well. + Default is \-1\.0 mm. +- **speed** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The speed for touch tip motion, in mm/s. + + - Default: 60\.0 mm/s + - Maximum: 80\.0 mm/s + - Minimum: 1\.0 mm/s + +Raises: +`UnexpectedTipRemovalError` – If no tip is attached to the pipette. + +Raises: +[**RuntimeError**](https://docs.python.org/3/library/exceptions.html#RuntimeError '(in Python v3.12)') – If no location is specified and the location cache is +`None`. This should happen if `touch_tip` is called +without first calling a method that takes a location, like +[`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') or [`dispense()`](#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense'). + +Returns: +This instance. + +New in version 2\.0\. + +transfer(_self_, _volume: 'Union\[float_, _Sequence\[float]]'_, _source: 'AdvancedLiquidHandling'_, _dest: 'AdvancedLiquidHandling'_, _trash: 'bool' \= True_, _\\\*\\\*kwargs: 'Any'_) → 'InstrumentContext' +Move liquid from one well or group of wells to another. + +Transfer is a higher\-level command, incorporating other +[`InstrumentContext`](#opentrons.protocol_api.InstrumentContext 'opentrons.protocol_api.InstrumentContext') commands, like [`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') and +[`dispense()`](#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense'). It makes writing a protocol easier at the cost of +specificity. See [Complex Commands](index.html#v2-complex-commands) for details on how transfer and +other complex commands perform their component steps. + +Parameters: + +- **volume** – The amount, in µL, to aspirate from each source and dispense to + each destination. If `volume` is a list, each amount will be + used for the source and destination at the matching index. A list + item of `0` will skip the corresponding wells entirely. See + [List of Volumes](index.html#complex-list-volumes) for details and examples. +- **source** – A single well or a list of wells to aspirate liquid from. +- **dest** – A single well or a list of wells to dispense liquid into. + +Keyword Arguments: +Transfer accepts a number of optional parameters that give +you greater control over the exact steps it performs. See +[Complex Liquid Handling Parameters](index.html#complex-params) or the links under each argument’s entry below for +additional details and examples. + +- **new_tip** (_string_) – + When to pick up and drop tips during the command. Defaults to `"once"`. + +> - `"once"`: Use one tip for the entire command. +> - `"always"`: Use a new tip for each set of aspirate and dispense steps. +> - `"never"`: Do not pick up or drop tips at all. + +See [Tip Handling](index.html#param-tip-handling) for details. + +- **trash** (_boolean_) – + If `True` (default), the pipette will drop tips in its + [`trash_container()`](#opentrons.protocol_api.InstrumentContext.trash_container 'opentrons.protocol_api.InstrumentContext.trash_container'). + If `False`, the pipette will return tips to their tip rack. + +See [Trash Tips](index.html#param-trash) for details. + +- **touch_tip** (_boolean_) – + If `True`, perform a [`touch_tip()`](#opentrons.protocol_api.InstrumentContext.touch_tip 'opentrons.protocol_api.InstrumentContext.touch_tip') following each + [`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') and [`dispense()`](#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense'). Defaults to `False`. + +See [Touch Tip](index.html#param-touch-tip) for details. + +- **blow_out** (_boolean_) – + If `True`, a [`blow_out()`](#opentrons.protocol_api.InstrumentContext.blow_out 'opentrons.protocol_api.InstrumentContext.blow_out') will occur following each + [`dispense()`](#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense'), but only if the pipette has no liquid left + in it. If `False` (default), the pipette will not blow out liquid. + +See [Blow Out](index.html#param-blow-out) for details. + +- **blowout_location** (_string_) – + Accepts one of three string values: `"trash"`, `"source well"`, or + `"destination well"`. + +If `blow_out` is `False` (its default), this parameter is ignored. + +If `blow_out` is `True` and this parameter is not set: + +> - Blow out into the trash, if the pipette is empty or only contains the +> disposal volume. +> - Blow out into the source well, if the pipette otherwise contains liquid. + +- **mix_before** (_tuple_) – + Perform a [`mix()`](#opentrons.protocol_api.InstrumentContext.mix 'opentrons.protocol_api.InstrumentContext.mix') before each [`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') during the + transfer. The first value of the tuple is the number of repetitions, and + the second value is the amount of liquid to mix in µL. + +See [Mix Before](index.html#param-mix-before) for details. + +- **mix_after** (_tuple_) – + Perform a [`mix()`](#opentrons.protocol_api.InstrumentContext.mix 'opentrons.protocol_api.InstrumentContext.mix') after each [`dispense()`](#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') during the + transfer. The first value of the tuple is the number of repetitions, and + the second value is the amount of liquid to mix in µL. + +See [Mix After](index.html#param-mix-after) for details. + +- **disposal_volume** (_float_) – + Transfer ignores the numeric value of this parameter. If set, the pipette + will not aspirate additional liquid, but it will perform a very small blow + out after each dispense. + +See [Disposal Volume](index.html#param-disposal-volume) for details. + +Returns: +This instance. + +New in version 2\.0\. + +_property_ trash_container*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware'), [TrashBin](index.html#opentrons.protocol_api.TrashBin 'opentrons.protocol_api.disposal_locations.TrashBin'), [WasteChute](index.html#opentrons.protocol_api.WasteChute 'opentrons.protocol_api.disposal_locations.WasteChute')]* +The trash container associated with this pipette. + +This is the property used to determine where to drop tips and blow out liquids +when calling [`drop_tip()`](#opentrons.protocol_api.InstrumentContext.drop_tip 'opentrons.protocol_api.InstrumentContext.drop_tip') or [`blow_out()`](#opentrons.protocol_api.InstrumentContext.blow_out 'opentrons.protocol_api.InstrumentContext.blow_out') without arguments. + +You can set this to a [`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware'), [`TrashBin`](#opentrons.protocol_api.TrashBin 'opentrons.protocol_api.TrashBin'), or [`WasteChute`](#opentrons.protocol_api.WasteChute 'opentrons.protocol_api.WasteChute'). + +The default value depends on the robot type and API version: + +- [`ProtocolContext.fixed_trash`](#opentrons.protocol_api.ProtocolContext.fixed_trash 'opentrons.protocol_api.ProtocolContext.fixed_trash'), if it exists. +- Otherwise, the first item previously loaded with + [`ProtocolContext.load_trash_bin()`](#opentrons.protocol_api.ProtocolContext.load_trash_bin 'opentrons.protocol_api.ProtocolContext.load_trash_bin') or + [`ProtocolContext.load_waste_chute()`](#opentrons.protocol_api.ProtocolContext.load_waste_chute 'opentrons.protocol_api.ProtocolContext.load_waste_chute'). + +Changed in version 2\.16: Added support for `TrashBin` and `WasteChute` objects. + +New in version 2\.0\. + +_property_ type*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +`'single'` if this is a 1\-channel pipette, or `'multi'` otherwise. + +See also [`channels`](#opentrons.protocol_api.InstrumentContext.channels 'opentrons.protocol_api.InstrumentContext.channels'), which can distinguish between 8\-channel and 96\-channel +pipettes. + +New in version 2\.0\. + +_property_ well_bottom_clearance*: Clearances* +The distance above the bottom of a well to aspirate or dispense. + +This is an object with attributes `aspirate` and `dispense`, describing the +default height of the corresponding operation. The default is 1\.0 mm for both +aspirate and dispense. + +When [`aspirate()`](#opentrons.protocol_api.InstrumentContext.aspirate 'opentrons.protocol_api.InstrumentContext.aspirate') or [`dispense()`](#opentrons.protocol_api.InstrumentContext.dispense 'opentrons.protocol_api.InstrumentContext.dispense') is given a [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') +rather than a full [`Location`](#opentrons.types.Location 'opentrons.types.Location'), the robot will move this distance +above the bottom of the well to aspirate or dispense. + +To change, set the corresponding attribute: + +``` +pipette.well_bottom_clearance.aspirate = 2 + +``` + +New in version 2\.0\. + +### Labware + +_class_ opentrons.protocol*api.Labware(\_core: AbstractLabware\[Any]*, _api_version: APIVersion_, _protocol_core: ProtocolCore_, _core_map: LoadedCoreMap_) +This class represents a piece of labware. + +Labware available in the API generally fall under two categories. + +> - Consumable labware: well plates, tubes in racks, reservoirs, tip racks, etc. +> - Adapters: durable items that hold other labware, either on modules or directly +> on the deck. + +The `Labware` class defines the physical geometry of the labware +and provides methods for [accessing wells](index.html#new-well-access) within the labware. + +Create `Labware` objects by calling the appropriate `load_labware()` method, +depending on where you are loading the labware. For example, to load labware on a +Thermocycler Module, use [`ThermocyclerContext.load_labware()`](#opentrons.protocol_api.ThermocyclerContext.load_labware 'opentrons.protocol_api.ThermocyclerContext.load_labware'). To load +labware directly on the deck, use [`ProtocolContext.load_labware()`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware'). See +[Loading Labware](index.html#loading-labware). + +_property_ api_version*: APIVersion* +See [`ProtocolContext.api_version`](#opentrons.protocol_api.ProtocolContext.api_version 'opentrons.protocol_api.ProtocolContext.api_version'). + +New in version 2\.0\. + +_property_ calibrated_offset*: [Point](index.html#opentrons.types.Point 'opentrons.types.Point')* +The front\-left\-bottom corner of the labware, including its labware offset. + +When running a protocol in the Opentrons App or on the touchscreen, Labware +Position Check sets the labware offset. + +New in version 2\.0\. + +_property_ child*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')]* +The labware (if any) present on this labware. + +New in version 2\.15\. + +columns(_self_, _\\\*args: 'Union\[int_, _str]'_) → 'List\[List\[Well]]' +Accessor function to navigate through a labware by column. + +Use indexing to access individual columns or wells contained in the nested list. +For example, access column 1 with `labware.columns()[0]`. +On a standard 96\-well plate, this will output a list of [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') +objects containing A1 through H1\. + +Note + +Using args with this method is deprecated. Use indexing instead. + +If your code uses args, they can be either strings or integers, but not a +mix of the two. For example, `.columns(1, 4)` or `.columns("1", "4")` is +valid, but `.columns("1", 4)` is not. + +Returns: +A list of column lists. + +New in version 2\.0\. + +columns*by_index(\_self*) → 'Dict\[str, List\[Well]]' + +Deprecated since version 2\.0: Use [`columns_by_name()`](#opentrons.protocol_api.Labware.columns_by_name 'opentrons.protocol_api.Labware.columns_by_name') instead. + +New in version 2\.0\. + +columns*by_name(\_self*) → 'Dict\[str, List\[Well]]' +Accessor function to navigate through a labware by column name. + +Use indexing to access individual columns or wells contained in the dictionary. +For example, access column 1 with `labware.columns_by_name()["1"]`. +On a standard 96\-well plate, this will output a list of [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') +objects containing A1 through H1\. + +Returns: +Dictionary of [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') lists keyed by column name. + +New in version 2\.0\. + +_property_ highest_z*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The z\-coordinate of the highest single point anywhere on the labware. + +This is taken from the `zDimension` property of the `dimensions` object in the +labware definition and takes into account the labware offset. + +New in version 2\.0\. + +_property_ is_adapter*: [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')* +Whether the labware behaves as an adapter. + +Returns `True` if the labware definition specifies `adapter` as one of the +labware’s `allowedRoles`. + +New in version 2\.15\. + +_property_ is_tiprack*: [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')* +Whether the labware behaves as a tip rack. + +Returns `True` if the labware definition specifies `isTiprack` as `True`. + +New in version 2\.0\. + +load*labware(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' +Load a compatible labware onto the labware using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_labware`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') (which loads labware directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded labware object. + +New in version 2\.15\. + +load*labware_from_definition(\_self*, _definition: 'LabwareDefinition'_, _label: 'Optional\[str]' \= None_) → 'Labware' +Load a compatible labware onto the labware using an inline definition. + +Parameters: + +- **definition** – The labware definition. +- **label** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional special name to give the labware. If specified, + this is how the labware will appear in the run log, Labware Position + Check, and elsewhere in the Opentrons App and on the touchscreen. + +Returns: +The initialized and loaded labware object. + +New in version 2\.15\. + +_property_ load_name*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The API load name of the labware definition. + +New in version 2\.0\. + +_property_ magdeck_engage_height*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +Return the default magnet engage height that +[`MagneticModuleContext.engage()`](#opentrons.protocol_api.MagneticModuleContext.engage 'opentrons.protocol_api.MagneticModuleContext.engage') will use for this labware. + +Warning + +This currently returns confusing and unpredictable results that do not +necessarily match what [`MagneticModuleContext.engage()`](#opentrons.protocol_api.MagneticModuleContext.engage 'opentrons.protocol_api.MagneticModuleContext.engage') will +actually choose for its default height. + +The confusion is related to how this height’s units and origin point are +defined, and differences between Magnetic Module generations. + +For now, we recommend you avoid accessing this property directly. + +New in version 2\.0\. + +_property_ name*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The display name of the labware. + +If you specified a value for `label` when loading the labware, `name` is +that value. + +Otherwise, it is the [`load_name`](#opentrons.protocol_api.Labware.load_name 'opentrons.protocol_api.Labware.load_name') of the labware. + +New in version 2\.0\. + +_property_ parameters*: LabwareParameters* +Internal properties of a labware including type and quirks. + +New in version 2\.0\. + +_property_ parent*: Union\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)'), [Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware'), ModuleTypes, OffDeckType]* +Where the labware is loaded. + +This corresponds to the physical object that the labware _directly_ rests upon. + +Returns: +If the labware is directly on the robot’s deck, the `str` name of the deck slot, +like `"D1"` (Flex) or `"1"` (OT\-2\). See [Deck Slots](index.html#deck-slots). + +If the labware is on a module, a module context. + +If the labware is on a labware or adapter, a [`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware'). + +If the labware is off\-deck, [`OFF_DECK`](#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK'). + +Changed in version 2\.14: Return type for module parent changed. +Formerly, the API returned an internal geometry interface. + +Changed in version 2\.15: Returns a [`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') if the labware is loaded onto a labware/adapter. +Returns [`OFF_DECK`](#opentrons.protocol_api.OFF_DECK 'opentrons.protocol_api.OFF_DECK') if the labware is off\-deck. +Formerly, if the labware was removed by using `del` on [`deck`](#opentrons.protocol_api.ProtocolContext.deck 'opentrons.protocol_api.ProtocolContext.deck'), +this would return where it was before its removal. + +New in version 2\.0\. + +_property_ quirks*: [List](https://docs.python.org/3/library/typing.html#typing.List '(in Python v3.12)')\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')]* +Quirks specific to this labware. + +New in version 2\.0\. + +reset(_self_) → 'None' +Reset tip tracking for a tip rack. + +After resetting, the API treats all wells on the rack as if they contain unused tips. +This is useful if you want to reuse tips after calling [`return_tip()`](#opentrons.protocol_api.InstrumentContext.return_tip 'opentrons.protocol_api.InstrumentContext.return_tip'). + +If you need to physically replace an empty tip rack in the middle of your protocol, +use [`move_labware()`](#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware') instead. See [The Off\-Deck Location](index.html#off-deck-location) for an example. + +Changed in version 2\.14: This method will raise an exception if you call it on a labware that isn’t +a tip rack. Formerly, it would do nothing. + +New in version 2\.0\. + +rows(_self_, _\\\*args: 'Union\[int_, _str]'_) → 'List\[List\[Well]]' +Accessor function to navigate through a labware by row. + +Use indexing to access individual rows or wells contained in the nested list. +On a standard 96\-well plate, this will output a list of [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') +objects containing A1 through A12\. + +Note + +Using args with this method is deprecated. Use indexing instead. + +If your code uses args, they can be either strings or integers, but not a +mix of the two. For example, `.rows(1, 4)` or `.rows("1", "4")` is +valid, but `.rows("1", 4)` is not. + +Returns: +A list of row lists. + +New in version 2\.0\. + +rows*by_index(\_self*) → 'Dict\[str, List\[Well]]' + +Deprecated since version 2\.0: Use [`rows_by_name()`](#opentrons.protocol_api.Labware.rows_by_name 'opentrons.protocol_api.Labware.rows_by_name') instead. + +New in version 2\.0\. + +rows*by_name(\_self*) → 'Dict\[str, List\[Well]]' +Accessor function to navigate through a labware by row name. + +Use indexing to access individual rows or wells contained in the dictionary. +For example, access row A with `labware.rows_by_name()["A"]`. +On a standard 96\-well plate, this will output a list of [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') +objects containing A1 through A12\. + +Returns: +Dictionary of [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') lists keyed by row name. + +New in version 2\.0\. + +set*calibration(\_self*, _delta: 'Point'_) → 'None' +An internal, deprecated method used for updating the labware offset. + +Deprecated since version 2\.14\. + +set*offset(\_self*, _x: 'float'_, _y: 'float'_, _z: 'float'_) → 'None' +Set the labware’s position offset. + +The offset is an x, y, z vector in deck coordinates +(see [Position Relative to the Deck](index.html#protocol-api-deck-coords)). + +How the motion system applies the offset depends on the API level of the protocol. + +| API level | Offset behavior | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2\.12–2\.13 | Offsets only apply to the exact [`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') instance. | +| 2\.14–2\.17 | `set_offset()` is not available, and the API raises an error. | +| 2\.18 and newer | _ Offsets apply to any labware of the same type, in the same on\-deck location. _ Offsets can’t be set on labware that is currently off\-deck. \* Offsets do not follow a labware instance when using [`move_labware()`](#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware'). | + +Note + +Setting offsets with this method will override any labware offsets set +by running Labware Position Check in the Opentrons App. + +This method is designed for use with mechanisms like +[`opentrons.execute.get_protocol_api`](#opentrons.execute.get_protocol_api 'opentrons.execute.get_protocol_api'), which lack an interactive way +to adjust labware offsets. (See [Advanced Control](index.html#advanced-control).) + +Changed in version 2\.14: Temporarily removed. + +Changed in version 2\.18: Restored, and now applies to labware type–location pairs. + +New in version 2\.12\. + +_property_ tip_length*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +For a tip rack labware, the length of the tips it holds, in mm. + +This is taken from the `tipLength` property of the `parameters` object in the labware definition. + +This method will raise an exception if you call it on a labware that isn’t a tip rack. + +New in version 2\.0\. + +_property_ uri*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +A string fully identifying the labware. + +The URI has three parts and follows the pattern `"namespace/load_name/version"`. +For example, `opentrons/corning_96_wellplate_360ul_flat/2`. + +New in version 2\.0\. + +well(_self_, _idx: 'Union\[int, str]'_) → 'Well' +Deprecated. Use result of [`wells()`](#opentrons.protocol_api.Labware.wells 'opentrons.protocol_api.Labware.wells') or [`wells_by_name()`](#opentrons.protocol_api.Labware.wells_by_name 'opentrons.protocol_api.Labware.wells_by_name'). + +New in version 2\.0\. + +wells(_self_, _\\\*args: 'Union\[str_, _int]'_) → 'List\[Well]' +Accessor function to navigate a labware top to bottom, left to right. + +i.e., this method returns a list ordered A1, B1, C1…A2, B2, C2…. + +Use indexing to access individual wells contained in the list. +For example, access well A1 with `labware.wells()[0]`. + +Note + +Using args with this method is deprecated. Use indexing instead. + +If your code uses args, they can be either strings or integers, but not a +mix of the two. For example, `.wells(1, 4)` or `.wells("1", "4")` is +valid, but `.wells("1", 4)` is not. + +Returns: +Ordered list of all wells in a labware. + +New in version 2\.0\. + +wells*by_index(\_self*) → 'Dict\[str, Well]' + +Deprecated since version 2\.0: Use [`wells_by_name()`](#opentrons.protocol_api.Labware.wells_by_name 'opentrons.protocol_api.Labware.wells_by_name') or dict access instead. + +New in version 2\.0\. + +wells*by_name(\_self*) → 'Dict\[str, Well]' +Accessor function used to navigate through a labware by well name. + +Use indexing to access individual wells contained in the dictionary. +For example, access well A1 with `labware.wells_by_name()["A1"]`. + +Returns: +Dictionary of [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') objects keyed by well name. + +New in version 2\.0\. + +_class_ opentrons.protocol_api.TrashBin +Represents a Flex or OT\-2 trash bin. + +See [`ProtocolContext.load_trash_bin()`](#opentrons.protocol_api.ProtocolContext.load_trash_bin 'opentrons.protocol_api.ProtocolContext.load_trash_bin'). + +top(_self_, _x: 'float' \= 0_, _y: 'float' \= 0_, _z: 'float' \= 0_) → 'TrashBin' +Add a location offset to a trash bin. + +The default location (`x`, `y`, and `z` all set to `0`) is the center of +the bin on the x\- and y\-axes, and slightly below its physical top on the z\-axis. + +Offsets can be positive or negative and are measured in mm. +See [Position Relative to the Deck](index.html#protocol-api-deck-coords). + +New in version 2\.18\. + +_class_ opentrons.protocol_api.WasteChute +Represents a Flex waste chute. + +See [`ProtocolContext.load_waste_chute()`](#opentrons.protocol_api.ProtocolContext.load_waste_chute 'opentrons.protocol_api.ProtocolContext.load_waste_chute'). + +top(_self_, _x: 'float' \= 0_, _y: 'float' \= 0_, _z: 'float' \= 0_) → 'WasteChute' +Add a location offset to a waste chute. + +The default location (`x`, `y`, and `z` all set to `0`) is the center of +the chute’s opening on the x\- and y\-axes, and slightly below its physical top +on the z\-axis. See [Waste Chute](index.html#configure-waste-chute) for more information on possible +configurations of the chute. + +Offsets can be positive or negative and are measured in mm. +See [Position Relative to the Deck](index.html#protocol-api-deck-coords). + +New in version 2\.18\. + +### Wells and Liquids + +_class_ opentrons.protocol*api.Well(\_parent: [Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware')*, _core: WellCore_, _api_version: APIVersion_) +The Well class represents a single well in a [`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware'). It provides parameters and functions for three major uses: + +> - Calculating positions relative to the well. See [Position Relative to Labware](index.html#position-relative-labware) for details. +> - Returning well measurements. See [Well Dimensions](index.html#new-labware-well-properties) for details. +> - Specifying what liquid should be in the well at the beginning of a protocol. See [Labeling Liquids in Wells](index.html#labeling-liquids) for details. + +_property_ api_version*: APIVersion* + +New in version 2\.0\. + +bottom(_self_, _z: 'float' \= 0\.0_) → 'Location' + +Parameters: +**z** – An offset on the z\-axis, in mm. Positive offsets are higher and +negative offsets are lower. + +Returns: +A [`Location`](#opentrons.types.Location 'opentrons.types.Location') corresponding to the +absolute position of the bottom\-center of the well, plus the `z` offset +(if specified). + +New in version 2\.0\. + +center(_self_) → 'Location' + +Returns: +A [`Location`](#opentrons.types.Location 'opentrons.types.Location') corresponding to the +absolute position of the center of the well (in all three dimensions). + +New in version 2\.0\. + +_property_ depth*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The depth, in mm, of a well along the z\-axis, from the very top of the well to +the very bottom. + +New in version 2\.9\. + +_property_ diameter*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +The diameter, in mm, of a circular well. Returns `None` +if the well is not circular. + +New in version 2\.0\. + +_property_ display_name*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +A human\-readable name for the well, including labware and deck location. + +For example, “A1 of Corning 96 Well Plate 360 µL Flat on slot D1”. Run log +entries use this format for identifying wells. See +[`ProtocolContext.commands()`](#opentrons.protocol_api.ProtocolContext.commands 'opentrons.protocol_api.ProtocolContext.commands'). + +from*center_cartesian(\_self*, _x: 'float'_, _y: 'float'_, _z: 'float'_) → 'Point' +Specifies a [`Point`](#opentrons.types.Point 'opentrons.types.Point') based on fractions of the +distance from the center of the well to the edge along each axis. + +For example, `from_center_cartesian(0, 0, 0.5)` specifies a point at the +well’s center on the x\- and y\-axis, and half of the distance from the center of +the well to its top along the z\-axis. To move the pipette to that location, +construct a [`Location`](#opentrons.types.Location 'opentrons.types.Location') relative to the same well: + +``` +location = types.Location( + plate["A1"].from_center_cartesian(0, 0, 0.5), plate["A1"] +) +pipette.move_to(location) + +``` + +See [Points and Locations](index.html#points-locations) for more information. + +Parameters: + +- **x** – The fraction of the distance from the well’s center to its edge + along the x\-axis. Negative values are to the left, and positive values + are to the right. +- **y** – The fraction of the distance from the well’s center to its edge + along the y\-axis. Negative values are to the front, and positive values + are to the back. +- **z** – The fraction of the distance from the well’s center to its edge + along the x\-axis. Negative values are down, and positive values are up. + +Returns: +A [`Point`](#opentrons.types.Point 'opentrons.types.Point') representing the specified +position in absolute deck coordinates. + +Note + +Even if the absolute values of `x`, `y`, and `z` are all less +than 1, a location constructed from the well and the result of +`from_center_cartesian` may be outside of the physical well. For example, +`from_center_cartesian(0.9, 0.9, 0)` would be outside of a cylindrical +well, but inside a square well. + +New in version 2\.8\. + +_property_ has_tip*: [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)')* +Whether this well contains a tip. Always `False` if the parent labware +isn’t a tip rack. + +New in version 2\.0\. + +_property_ length*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +The length, in mm, of a rectangular well along the x\-axis (left to right). +Returns `None` if the well is not rectangular. + +New in version 2\.9\. + +load*liquid(\_self*, _liquid: 'Liquid'_, _volume: 'float'_) → 'None' +Load a liquid into a well. + +Parameters: + +- **liquid** ([_Liquid_](index.html#opentrons.protocol_api.Liquid 'opentrons.protocol_api.Liquid')) – The liquid to load into the well. +- **volume** ([_float_](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')) – The volume of liquid to load, in µL. + +New in version 2\.14\. + +_property_ max_volume*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The maximum volume, in µL, that the well can hold. + +This amount is set by the JSON labware definition, specifically the `totalLiquidVolume` property of the particular well. + +_property_ parent*: [Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')* +The [`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') object that the well is a part of. + +New in version 2\.0\. + +top(_self_, _z: 'float' \= 0\.0_) → 'Location' + +Parameters: +**z** – An offset on the z\-axis, in mm. Positive offsets are higher and +negative offsets are lower. + +Returns: +A [`Location`](#opentrons.types.Location 'opentrons.types.Location') corresponding to the +absolute position of the top\-center of the well, plus the `z` offset +(if specified). + +New in version 2\.0\. + +_property_ well_name*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +A string representing the well’s coordinates. + +For example, “A1” or “H12”. + +The format of strings that this property returns is the same format as the key +for [accessing wells in a dictionary](index.html#well-dictionary-access). + +New in version 2\.7\. + +_property_ width*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +The width, in mm, of a rectangular well along the y\-axis (front to back). +Returns `None` if the well is not rectangular. + +New in version 2\.9\. + +_class_ opentrons.protocol*api.Liquid(*\_id: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')_, \_name: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')_, _description: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')]_, _display_color: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')]_) +A liquid to load into a well. + +name +A human\-readable name for the liquid. + +Type: +[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)') + +description +An optional description. + +Type: +Optional\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')] + +display_color +An optional display color for the liquid. + +Type: +Optional\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')] + +New in version 2\.14\. + +### Modules + +_class_ opentrons.protocol*api.HeaterShakerContext(\_core: AbstractModuleCore*, _protocol_core: AbstractProtocol\[AbstractInstrument\[AbstractWellCore], AbstractLabware\[AbstractWellCore], AbstractModuleCore]_, _core_map: LoadedCoreMap_, _api_version: APIVersion_, _broker: LegacyBroker_) +An object representing a connected Heater\-Shaker Module. + +It should not be instantiated directly; instead, it should be +created through [`ProtocolContext.load_module()`](#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module'). + +New in version 2\.13\. + +_property_ api_version*: APIVersion* + +New in version 2\.0\. + +close*labware_latch(\_self*) → 'None' +Closes the labware latch. + +The labware latch needs to be closed using this method before sending a shake command, +even if the latch was manually closed before starting the protocol. + +New in version 2\.13\. + +_property_ current_speed*: [int](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)')* +The current speed of the Heater\-Shaker’s plate in rpm. + +New in version 2\.13\. + +_property_ current_temperature*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The current temperature of the Heater\-Shaker’s plate in °C. + +Returns `23` in simulation if no target temperature has been set. + +New in version 2\.13\. + +deactivate*heater(\_self*) → 'None' +Stops heating. + +The module will passively cool to room temperature. +The Heater\-Shaker does not have active cooling. + +New in version 2\.13\. + +deactivate*shaker(\_self*) → 'None' +Stops shaking. + +Decelerating to 0 rpm typically only takes a few seconds. + +New in version 2\.13\. + +_property_ labware*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')]* +The labware (if any) present on this module. + +New in version 2\.0\. + +_property_ labware_latch_status*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +One of six possible latch statuses: + +- `opening` – The latch is currently opening (in motion). +- `idle_open` – The latch is open and not moving. +- `closing` – The latch is currently closing (in motion). +- `idle_closed` – The latch is closed and not moving. +- `idle_unknown` – The default status upon reset, regardless of physical latch position. + Use [`close_labware_latch()`](#opentrons.protocol_api.HeaterShakerContext.close_labware_latch 'opentrons.protocol_api.HeaterShakerContext.close_labware_latch') before other commands + requiring confirmation that the latch is closed. +- `unknown` – The latch status can’t be determined. + +New in version 2\.13\. + +load*adapter(\_self*, _name: 'str'_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' +Load an adapter onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_adapter`](#opentrons.protocol_api.ProtocolContext.load_adapter 'opentrons.protocol_api.ProtocolContext.load_adapter') (which loads adapters directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded adapter object. + +New in version 2\.15\. + +load*adapter_from_definition(\_self*, _definition: 'LabwareDefinition'_) → 'Labware' +Load an adapter onto the module using an inline definition. + +Parameters: +**definition** – The labware definition. + +Returns: +The initialized and loaded labware object. + +New in version 2\.15\. + +load*labware(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_, _adapter: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_labware`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') (which loads labware directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded labware object. + +New in version 2\.1: The _label,_ _namespace,_ and _version_ parameters. + +load*labware_by_name(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' + +Deprecated since version 2\.0: Use [`load_labware()`](#opentrons.protocol_api.HeaterShakerContext.load_labware 'opentrons.protocol_api.HeaterShakerContext.load_labware') instead. + +New in version 2\.1\. + +load*labware_from_definition(\_self*, _definition: 'LabwareDefinition'_, _label: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using an inline definition. + +Parameters: + +- **definition** – The labware definition. +- **label** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional special name to give the labware. If + specified, this is the name the labware will appear + as in the run log and the calibration view in the + Opentrons app. + +Returns: +The initialized and loaded labware object. + +New in version 2\.0\. + +_property_ model*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleV1', 'magneticModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleV1', 'temperatureModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleV1', 'thermocyclerModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderV1']]* +Get the module’s model identifier. + +New in version 2\.14\. + +open*labware_latch(\_self*) → 'None' +Open the Heater\-Shaker’s labware latch. + +The labware latch needs to be closed before:\* Shaking + +- Pipetting to or from the labware on the Heater\-Shaker +- Pipetting to or from labware to the left or right of the Heater\-Shaker + +Attempting to open the latch while the Heater\-Shaker is shaking will raise an error. + +Note + +Before opening the latch, this command will retract the pipettes upward +if they are parked adjacent to the left or right of the Heater\-Shaker. + +New in version 2\.13\. + +_property_ parent*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The name of the slot the module is on. + +On a Flex, this will be like `"D1"`. On an OT\-2, this will be like `"1"`. +See [Deck Slots](index.html#deck-slots). + +New in version 2\.14\. + +_property_ serial_number*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +Get the module’s unique hardware serial number. + +New in version 2\.14\. + +set*and_wait_for_shake_speed(\_self*, _rpm: 'int'_) → 'None' +Set a shake speed in rpm and block execution of further commands until the module reaches the target. + +Reaching a target shake speed typically only takes a few seconds. + +Note + +Before shaking, this command will retract the pipettes upward if they are parked adjacent to the Heater\-Shaker. + +Parameters: +**rpm** – A value between 200 and 3000, representing the target shake speed in revolutions per minute. + +New in version 2\.13\. + +set*and_wait_for_temperature(\_self*, _celsius: 'float'_) → 'None' +Set a target temperature and wait until the module reaches the target. + +No other protocol commands will execute while waiting for the temperature. + +Parameters: +**celsius** – A value between 27 and 95, representing the target temperature in °C. +Values are automatically truncated to two decimal places, +and the Heater\-Shaker module has a temperature accuracy of ±0\.5 °C. + +New in version 2\.13\. + +set*target_temperature(\_self*, _celsius: 'float'_) → 'None' +Set target temperature and return immediately. + +Sets the Heater\-Shaker’s target temperature and returns immediately without +waiting for the target to be reached. Does not delay the protocol until +target temperature has reached. +Use [`wait_for_temperature()`](#opentrons.protocol_api.HeaterShakerContext.wait_for_temperature 'opentrons.protocol_api.HeaterShakerContext.wait_for_temperature') to delay +protocol execution. + +Parameters: +**celsius** – A value between 27 and 95, representing the target temperature in °C. +Values are automatically truncated to two decimal places, +and the Heater\-Shaker module has a temperature accuracy of ±0\.5 °C. + +New in version 2\.13\. + +_property_ speed_status*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +One of five possible shaking statuses: + +- `holding at target` – The module has reached its target shake speed + and is actively maintaining that speed. +- `speeding up` – The module is increasing its shake speed towards a target. +- `slowing down` – The module was previously shaking at a faster speed + and is currently reducing its speed to a lower target or to deactivate. +- `idle` – The module is not shaking. +- `error` – The shaking status can’t be determined. + +New in version 2\.13\. + +_property_ target_speed*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[int](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)')]* +Target speed of the Heater\-Shaker’s plate in rpm. + +New in version 2\.13\. + +_property_ target_temperature*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +The target temperature of the Heater\-Shaker’s plate in °C. + +Returns `None` if no target has been set. + +New in version 2\.13\. + +_property_ temperature_status*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +One of five possible temperature statuses: + +- `holding at target` – The module has reached its target temperature + and is actively maintaining that temperature. +- `cooling` – The module has previously heated and is now passively cooling. + The Heater\-Shaker does not have active cooling. +- `heating` – The module is heating to a target temperature. +- `idle` – The module has not heated since the beginning of the protocol. +- `error` – The temperature status can’t be determined. + +New in version 2\.13\. + +_property_ type*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderType']]* +Get the module’s general type identifier. + +New in version 2\.14\. + +wait*for_temperature(\_self*) → 'None' +Delays protocol execution until the Heater\-Shaker has reached its target +temperature. + +Raises an error if no target temperature was previously set. + +New in version 2\.13\. + +_class_ opentrons.protocol*api.MagneticBlockContext(\_core: AbstractModuleCore*, _protocol_core: AbstractProtocol\[AbstractInstrument\[AbstractWellCore], AbstractLabware\[AbstractWellCore], AbstractModuleCore]_, _core_map: LoadedCoreMap_, _api_version: APIVersion_, _broker: LegacyBroker_) +An object representing a Magnetic Block. + +It should not be instantiated directly; instead, it should be +created through [`ProtocolContext.load_module()`](#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module'). + +New in version 2\.15\. + +_property_ api_version*: APIVersion* + +New in version 2\.0\. + +_property_ labware*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')]* +The labware (if any) present on this module. + +New in version 2\.0\. + +load*adapter(\_self*, _name: 'str'_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' +Load an adapter onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_adapter`](#opentrons.protocol_api.ProtocolContext.load_adapter 'opentrons.protocol_api.ProtocolContext.load_adapter') (which loads adapters directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded adapter object. + +New in version 2\.15\. + +load*adapter_from_definition(\_self*, _definition: 'LabwareDefinition'_) → 'Labware' +Load an adapter onto the module using an inline definition. + +Parameters: +**definition** – The labware definition. + +Returns: +The initialized and loaded labware object. + +New in version 2\.15\. + +load*labware(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_, _adapter: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_labware`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') (which loads labware directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded labware object. + +New in version 2\.1: The _label,_ _namespace,_ and _version_ parameters. + +load*labware_by_name(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' + +Deprecated since version 2\.0: Use [`load_labware()`](#opentrons.protocol_api.MagneticBlockContext.load_labware 'opentrons.protocol_api.MagneticBlockContext.load_labware') instead. + +New in version 2\.1\. + +load*labware_from_definition(\_self*, _definition: 'LabwareDefinition'_, _label: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using an inline definition. + +Parameters: + +- **definition** – The labware definition. +- **label** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional special name to give the labware. If + specified, this is the name the labware will appear + as in the run log and the calibration view in the + Opentrons app. + +Returns: +The initialized and loaded labware object. + +New in version 2\.0\. + +_property_ model*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleV1', 'magneticModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleV1', 'temperatureModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleV1', 'thermocyclerModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderV1']]* +Get the module’s model identifier. + +New in version 2\.14\. + +_property_ parent*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The name of the slot the module is on. + +On a Flex, this will be like `"D1"`. On an OT\-2, this will be like `"1"`. +See [Deck Slots](index.html#deck-slots). + +New in version 2\.14\. + +_property_ type*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderType']]* +Get the module’s general type identifier. + +New in version 2\.14\. + +_class_ opentrons.protocol*api.MagneticModuleContext(\_core: AbstractModuleCore*, _protocol_core: AbstractProtocol\[AbstractInstrument\[AbstractWellCore], AbstractLabware\[AbstractWellCore], AbstractModuleCore]_, _core_map: LoadedCoreMap_, _api_version: APIVersion_, _broker: LegacyBroker_) +An object representing a connected Magnetic Module. + +It should not be instantiated directly; instead, it should be +created through [`ProtocolContext.load_module()`](#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module'). + +New in version 2\.0\. + +_property_ api_version*: APIVersion* + +New in version 2\.0\. + +disengage(_self_) → 'None' +Lower the magnets back into the Magnetic Module. + +New in version 2\.0\. + +engage(_self_, _height: 'Optional\[float]' \= None_, _offset: 'Optional\[float]' \= None_, _height_from_base: 'Optional\[float]' \= None_) → 'None' +Raise the Magnetic Module’s magnets. You can specify how high the magnets +should move: + +> - No parameter: Move to the default height for the loaded labware. If +> the loaded labware has no default, or if no labware is loaded, this will +> raise an error. +> - `height_from_base` – Move this many millimeters above the bottom +> of the labware. Acceptable values are between `0` and `25`. +> +> This is the recommended way to adjust the magnets’ height. +> +> New in version 2\.2\. +> +> - `offset` – Move this many millimeters above (positive value) or below +> (negative value) the default height for the loaded labware. The sum of +> the default height and `offset` must be between 0 and 25\. +> - `height` – Intended to move this many millimeters above the magnets’ +> home position. However, depending on the generation of module and the loaded +> labware, this may produce unpredictable results. You should normally use +> `height_from_base` instead. +> +> Changed in version 2\.14: This parameter has been removed. + +You shouldn’t specify more than one of these parameters. However, if you do, +their order of precedence is `height`, then `height_from_base`, then `offset`. + +New in version 2\.0\. + +_property_ labware*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')]* +The labware (if any) present on this module. + +New in version 2\.0\. + +load*adapter(\_self*, _name: 'str'_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' +Load an adapter onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_adapter`](#opentrons.protocol_api.ProtocolContext.load_adapter 'opentrons.protocol_api.ProtocolContext.load_adapter') (which loads adapters directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded adapter object. + +New in version 2\.15\. + +load*adapter_from_definition(\_self*, _definition: 'LabwareDefinition'_) → 'Labware' +Load an adapter onto the module using an inline definition. + +Parameters: +**definition** – The labware definition. + +Returns: +The initialized and loaded labware object. + +New in version 2\.15\. + +load*labware(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_, _adapter: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_labware`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') (which loads labware directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded labware object. + +New in version 2\.1: The _label,_ _namespace,_ and _version_ parameters. + +load*labware_by_name(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' + +Deprecated since version 2\.0: Use [`load_labware()`](#opentrons.protocol_api.MagneticModuleContext.load_labware 'opentrons.protocol_api.MagneticModuleContext.load_labware') instead. + +New in version 2\.1\. + +load*labware_from_definition(\_self*, _definition: 'LabwareDefinition'_, _label: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using an inline definition. + +Parameters: + +- **definition** – The labware definition. +- **label** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional special name to give the labware. If + specified, this is the name the labware will appear + as in the run log and the calibration view in the + Opentrons app. + +Returns: +The initialized and loaded labware object. + +New in version 2\.0\. + +_property_ model*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleV1', 'magneticModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleV1', 'temperatureModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleV1', 'thermocyclerModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderV1']]* +Get the module’s model identifier. + +New in version 2\.14\. + +_property_ parent*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The name of the slot the module is on. + +On a Flex, this will be like `"D1"`. On an OT\-2, this will be like `"1"`. +See [Deck Slots](index.html#deck-slots). + +New in version 2\.14\. + +_property_ serial_number*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +Get the module’s unique hardware serial number. + +New in version 2\.14\. + +_property_ status*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The status of the module, either `engaged` or `disengaged`. + +New in version 2\.0\. + +_property_ type*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderType']]* +Get the module’s general type identifier. + +New in version 2\.14\. + +_class_ opentrons.protocol*api.TemperatureModuleContext(\_core: AbstractModuleCore*, _protocol_core: AbstractProtocol\[AbstractInstrument\[AbstractWellCore], AbstractLabware\[AbstractWellCore], AbstractModuleCore]_, _core_map: LoadedCoreMap_, _api_version: APIVersion_, _broker: LegacyBroker_) +An object representing a connected Temperature Module. + +It should not be instantiated directly; instead, it should be +created through [`ProtocolContext.load_module()`](#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module'). + +New in version 2\.0\. + +_property_ api_version*: APIVersion* + +New in version 2\.0\. + +deactivate(_self_) → 'None' +Stop heating or cooling, and turn off the fan. + +New in version 2\.0\. + +_property_ labware*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')]* +The labware (if any) present on this module. + +New in version 2\.0\. + +load*adapter(\_self*, _name: 'str'_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' +Load an adapter onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_adapter`](#opentrons.protocol_api.ProtocolContext.load_adapter 'opentrons.protocol_api.ProtocolContext.load_adapter') (which loads adapters directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded adapter object. + +New in version 2\.15\. + +load*adapter_from_definition(\_self*, _definition: 'LabwareDefinition'_) → 'Labware' +Load an adapter onto the module using an inline definition. + +Parameters: +**definition** – The labware definition. + +Returns: +The initialized and loaded labware object. + +New in version 2\.15\. + +load*labware(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_, _adapter: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_labware`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') (which loads labware directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded labware object. + +New in version 2\.1: The _label,_ _namespace,_ and _version_ parameters. + +load*labware_by_name(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' + +Deprecated since version 2\.0: Use [`load_labware()`](#opentrons.protocol_api.TemperatureModuleContext.load_labware 'opentrons.protocol_api.TemperatureModuleContext.load_labware') instead. + +New in version 2\.1\. + +load*labware_from_definition(\_self*, _definition: 'LabwareDefinition'_, _label: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using an inline definition. + +Parameters: + +- **definition** – The labware definition. +- **label** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional special name to give the labware. If + specified, this is the name the labware will appear + as in the run log and the calibration view in the + Opentrons app. + +Returns: +The initialized and loaded labware object. + +New in version 2\.0\. + +_property_ model*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleV1', 'magneticModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleV1', 'temperatureModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleV1', 'thermocyclerModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderV1']]* +Get the module’s model identifier. + +New in version 2\.14\. + +_property_ parent*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The name of the slot the module is on. + +On a Flex, this will be like `"D1"`. On an OT\-2, this will be like `"1"`. +See [Deck Slots](index.html#deck-slots). + +New in version 2\.14\. + +_property_ serial_number*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +Get the module’s unique hardware serial number. + +New in version 2\.14\. + +set*temperature(\_self*, _celsius: 'float'_) → 'None' +Set a target temperature and wait until the module reaches the target. + +No other protocol commands will execute while waiting for the temperature. + +Parameters: +**celsius** – A value between 4 and 95, representing the target temperature in °C. + +New in version 2\.0\. + +_property_ status*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +One of four possible temperature statuses: + +- `holding at target` – The module has reached its target temperature + and is actively maintaining that temperature. +- `cooling` – The module is cooling to a target temperature. +- `heating` – The module is heating to a target temperature. +- `idle` – The module has been deactivated. + +New in version 2\.3\. + +_property_ target*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +The target temperature of the Temperature Module’s deck in °C. + +Returns `None` if no target has been set. + +New in version 2\.0\. + +_property_ temperature*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +The current temperature of the Temperature Module’s deck in °C. + +Returns `0` in simulation if no target temperature has been set. + +New in version 2\.0\. + +_property_ type*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderType']]* +Get the module’s general type identifier. + +New in version 2\.14\. + +_class_ opentrons.protocol*api.ThermocyclerContext(\_core: AbstractModuleCore*, _protocol_core: AbstractProtocol\[AbstractInstrument\[AbstractWellCore], AbstractLabware\[AbstractWellCore], AbstractModuleCore]_, _core_map: LoadedCoreMap_, _api_version: APIVersion_, _broker: LegacyBroker_) +An object representing a connected Thermocycler Module. + +It should not be instantiated directly; instead, it should be +created through [`ProtocolContext.load_module()`](#opentrons.protocol_api.ProtocolContext.load_module 'opentrons.protocol_api.ProtocolContext.load_module'). + +New in version 2\.0\. + +_property_ api_version*: APIVersion* + +New in version 2\.0\. + +_property_ block_target_temperature*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +The target temperature of the well block in °C. + +New in version 2\.0\. + +_property_ block_temperature*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +The current temperature of the well block in °C. + +New in version 2\.0\. + +_property_ block_temperature_status*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +One of five possible temperature statuses: + +- `holding at target` – The block has reached its target temperature + and is actively maintaining that temperature. +- `cooling` – The block is cooling to a target temperature. +- `heating` – The block is heating to a target temperature. +- `idle` – The block is not currently heating or cooling. +- `error` – The temperature status can’t be determined. + +New in version 2\.0\. + +close*lid(\_self*) → 'str' +Close the lid. + +New in version 2\.0\. + +deactivate(_self_) → 'None' +Turn off both the well block temperature controller and the lid heater. + +New in version 2\.0\. + +deactivate*block(\_self*) → 'None' +Turn off the well block temperature controller. + +New in version 2\.0\. + +deactivate*lid(\_self*) → 'None' +Turn off the lid heater. + +New in version 2\.0\. + +execute*profile(\_self*, _steps: 'List\[ThermocyclerStep]'_, _repetitions: 'int'_, _block_max_volume: 'Optional\[float]' \= None_) → 'None' +Execute a Thermocycler profile, defined as a cycle of +`steps`, for a given number of `repetitions`. + +Parameters: + +- **steps** – List of unique steps that make up a single cycle. + Each list item should be a dictionary that maps to + the parameters of the [`set_block_temperature()`](#opentrons.protocol_api.ThermocyclerContext.set_block_temperature 'opentrons.protocol_api.ThermocyclerContext.set_block_temperature') + method with a `temperature` key, and either or both of + `hold_time_seconds` and `hold_time_minutes`. +- **repetitions** – The number of times to repeat the cycled steps. +- **block_max_volume** – The greatest volume of liquid contained in any + individual well of the loaded labware, in µL. + If not specified, the default is 25 µL. + +New in version 2\.0\. + +_property_ labware*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[Labware](index.html#opentrons.protocol_api.Labware 'opentrons.protocol_api.labware.Labware')]* +The labware (if any) present on this module. + +New in version 2\.0\. + +_property_ lid_position*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')]* +One of these possible lid statuses: + +- `closed` – The lid is closed. +- `in_between` – The lid is neither open nor closed. +- `open` – The lid is open. +- `unknown` – The lid position can’t be determined. + +New in version 2\.0\. + +_property_ lid_target_temperature*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +The target temperature of the lid in °C. + +New in version 2\.0\. + +_property_ lid_temperature*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')]* +The current temperature of the lid in °C. + +New in version 2\.0\. + +_property_ lid_temperature_status*: [Optional](https://docs.python.org/3/library/typing.html#typing.Optional '(in Python v3.12)')\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')]* +One of five possible temperature statuses: + +- `holding at target` – The lid has reached its target temperature + and is actively maintaining that temperature. +- `cooling` – The lid has previously heated and is now passively cooling.The Thermocycler lid does not have active cooling. +- `heating` – The lid is heating to a target temperature. +- `idle` – The lid has not heated since the beginning of the protocol. +- `error` – The temperature status can’t be determined. + +New in version 2\.0\. + +load*adapter(\_self*, _name: 'str'_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' +Load an adapter onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_adapter`](#opentrons.protocol_api.ProtocolContext.load_adapter 'opentrons.protocol_api.ProtocolContext.load_adapter') (which loads adapters directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded adapter object. + +New in version 2\.15\. + +load*adapter_from_definition(\_self*, _definition: 'LabwareDefinition'_) → 'Labware' +Load an adapter onto the module using an inline definition. + +Parameters: +**definition** – The labware definition. + +Returns: +The initialized and loaded labware object. + +New in version 2\.15\. + +load*labware(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_, _adapter: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using its load parameters. + +The parameters of this function behave like those of +[`ProtocolContext.load_labware`](#opentrons.protocol_api.ProtocolContext.load_labware 'opentrons.protocol_api.ProtocolContext.load_labware') (which loads labware directly +onto the deck). Note that the parameter `name` here corresponds to +`load_name` on the `ProtocolContext` function. + +Returns: +The initialized and loaded labware object. + +New in version 2\.1: The _label,_ _namespace,_ and _version_ parameters. + +load*labware_by_name(\_self*, _name: 'str'_, _label: 'Optional\[str]' \= None_, _namespace: 'Optional\[str]' \= None_, _version: 'Optional\[int]' \= None_) → 'Labware' + +Deprecated since version 2\.0: Use [`load_labware()`](#opentrons.protocol_api.ThermocyclerContext.load_labware 'opentrons.protocol_api.ThermocyclerContext.load_labware') instead. + +New in version 2\.1\. + +load*labware_from_definition(\_self*, _definition: 'LabwareDefinition'_, _label: 'Optional\[str]' \= None_) → 'Labware' +Load a labware onto the module using an inline definition. + +Parameters: + +- **definition** – The labware definition. +- **label** ([_str_](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')) – An optional special name to give the labware. If + specified, this is the name the labware will appear + as in the run log and the calibration view in the + Opentrons app. + +Returns: +The initialized and loaded labware object. + +New in version 2\.0\. + +_property_ model*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleV1', 'magneticModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleV1', 'temperatureModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleV1', 'thermocyclerModuleV2'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockV1'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderV1']]* +Get the module’s model identifier. + +New in version 2\.14\. + +open*lid(\_self*) → 'str' +Open the lid. + +New in version 2\.0\. + +_property_ parent*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +The name of the slot the module is on. + +On a Flex, this will be like `"D1"`. On an OT\-2, this will be like `"1"`. +See [Deck Slots](index.html#deck-slots). + +New in version 2\.14\. + +_property_ serial_number*: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')* +Get the module’s unique hardware serial number. + +New in version 2\.14\. + +set*block_temperature(\_self*, _temperature: 'float'_, _hold_time_seconds: 'Optional\[float]' \= None_, _hold_time_minutes: 'Optional\[float]' \= None_, _ramp_rate: 'Optional\[float]' \= None_, _block_max_volume: 'Optional\[float]' \= None_) → 'None' +Set the target temperature for the well block, in °C. + +Parameters: + +- **temperature** – A value between 4 and 99, representing the target + temperature in °C. +- **hold_time_minutes** – The number of minutes to hold, after reaching + `temperature`, before proceeding to the + next command. If `hold_time_seconds` is also + specified, the times are added together. +- **hold_time_seconds** – The number of seconds to hold, after reaching + `temperature`, before proceeding to the + next command. If `hold_time_minutes` is also + specified, the times are added together. +- **block_max_volume** – The greatest volume of liquid contained in any + individual well of the loaded labware, in µL. + If not specified, the default is 25 µL. + +New in version 2\.0\. + +set*lid_temperature(\_self*, _temperature: 'float'_) → 'None' +Set the target temperature for the heated lid, in °C. + +Parameters: +**temperature** – A value between 37 and 110, representing the target +temperature in °C. + +New in version 2\.0\. + +_property_ type*: [Union](https://docs.python.org/3/library/typing.html#typing.Union '(in Python v3.12)')\[[Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['temperatureModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['thermocyclerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['heaterShakerModuleType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['magneticBlockType'], [Literal](https://docs.python.org/3/library/typing.html#typing.Literal '(in Python v3.12)')\['absorbanceReaderType']]* +Get the module’s general type identifier. + +New in version 2\.14\. + +### Useful Types + +_class_ opentrons.types.Location(_point: [Point](index.html#opentrons.types.Point 'opentrons.types.Point')_, _labware: Union\['Labware', 'Well', [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)'), 'ModuleGeometry', LabwareLike, [None](https://docs.python.org/3/library/constants.html#None '(in Python v3.12)'), 'ModuleContext']_) +A location to target as a motion. + +The location contains a [`Point`](#opentrons.types.Point 'opentrons.types.Point') (in +[Position Relative to the Deck](index.html#protocol-api-deck-coords)) and possibly an associated +[`Labware`](#opentrons.protocol_api.Labware 'opentrons.protocol_api.Labware') or [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') instance. + +It should rarely be constructed directly by the user; rather, it is the +return type of most [`Well`](#opentrons.protocol_api.Well 'opentrons.protocol_api.Well') accessors like [`Well.top()`](#opentrons.protocol_api.Well.top 'opentrons.protocol_api.Well.top') +and is passed directly into a method like `InstrumentContext.aspirate()`. + +Warning + +The `.labware` attribute of this class is used by the protocol +API internals to, among other things, determine safe heights to retract +the instruments to when moving between locations. If constructing an +instance of this class manually, be sure to either specify `None` as the +labware (so the robot does its worst case retraction) or specify the +correct labware for the `.point` attribute. + +Warning + +The `==` operation compares both the position and associated labware. +If you only need to compare locations, compare the `.point` +of each item. + +move(_self_, _point: 'Point'_) → "'Location'" +Alter the point stored in the location while preserving the labware. + +This returns a new Location and does not alter the current one. It +should be used like + +``` +>>> loc = Location(Point(1, 1, 1), None) +>>> new_loc = loc.move(Point(1, 1, 1)) +>>> +>>> # The new point is the old one plus the given offset. +>>> assert new_loc.point == Point(2, 2, 2) # True +>>> +>>> # The old point hasn't changed. +>>> assert loc.point == Point(1, 1, 1) # True + +``` + +_class_ opentrons.types.Mount(_value_) +An enumeration. + +_exception_ opentrons.types.PipetteNotAttachedError +An error raised if a pipette is accessed that is not attached + +_class_ opentrons.types.Point(_x_, _y_, _z_) + +x*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +Alias for field number 0 + +y*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +Alias for field number 1 + +z*: [float](https://docs.python.org/3/library/functions.html#float '(in Python v3.12)')* +Alias for field number 2 + +opentrons.protocol_api.OFF_DECK +A special location value, indicating that a labware is not currently on the robot’s deck. + +See [The Off\-Deck Location](index.html#off-deck-location) for details on using `OFF_DECK` with [`ProtocolContext.move_labware()`](#opentrons.protocol_api.ProtocolContext.move_labware 'opentrons.protocol_api.ProtocolContext.move_labware'). + +### Executing and Simulating Protocols + +opentrons.execute: functions and entrypoint for running protocols + +This module has functions that can be imported to provide protocol +contexts for running protocols during interactive sessions like Jupyter or just +regular python shells. It also provides a console entrypoint for running a +protocol from the command line. + +opentrons.execute.execute(_protocol_file: Union\[BinaryIO, TextIO]_, _protocol_name: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')_, _propagate_logs: [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)') \= False_, _log_level: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)') \= 'warning'_, _emit_runlog: Optional\[Callable\[\[Union\[opentrons.legacy_commands.types.DropTipMessage, opentrons.legacy_commands.types.DropTipInDisposalLocationMessage, opentrons.legacy_commands.types.PickUpTipMessage, opentrons.legacy_commands.types.ReturnTipMessage, opentrons.legacy_commands.types.AirGapMessage, opentrons.legacy_commands.types.TouchTipMessage, opentrons.legacy_commands.types.BlowOutMessage, opentrons.legacy_commands.types.BlowOutInDisposalLocationMessage, opentrons.legacy_commands.types.MixMessage, opentrons.legacy_commands.types.TransferMessage, opentrons.legacy_commands.types.DistributeMessage, opentrons.legacy_commands.types.ConsolidateMessage, opentrons.legacy_commands.types.DispenseMessage, opentrons.legacy_commands.types.DispenseInDisposalLocationMessage, opentrons.legacy_commands.types.AspirateMessage, opentrons.legacy_commands.types.HomeMessage, opentrons.legacy_commands.types.HeaterShakerSetTargetTemperatureMessage, opentrons.legacy_commands.types.HeaterShakerWaitForTemperatureMessage, opentrons.legacy_commands.types.HeaterShakerSetAndWaitForShakeSpeedMessage, opentrons.legacy_commands.types.HeaterShakerOpenLabwareLatchMessage, opentrons.legacy_commands.types.HeaterShakerCloseLabwareLatchMessage, opentrons.legacy_commands.types.HeaterShakerDeactivateShakerMessage, opentrons.legacy_commands.types.HeaterShakerDeactivateHeaterMessage, opentrons.legacy_commands.types.ThermocyclerCloseMessage, opentrons.legacy_commands.types.ThermocyclerWaitForLidTempMessage, opentrons.legacy_commands.types.ThermocyclerDeactivateMessage, opentrons.legacy_commands.types.ThermocyclerDeactivateBlockMessage, opentrons.legacy_commands.types.ThermocyclerDeactivateLidMessage, opentrons.legacy_commands.types.ThermocyclerSetLidTempMessage, opentrons.legacy_commands.types.ThermocyclerWaitForTempMessage, opentrons.legacy_commands.types.ThermocyclerWaitForHoldMessage, opentrons.legacy_commands.types.ThermocyclerExecuteProfileMessage, opentrons.legacy_commands.types.ThermocyclerSetBlockTempMessage, opentrons.legacy_commands.types.ThermocyclerOpenMessage, opentrons.legacy_commands.types.TempdeckSetTempMessage, opentrons.legacy_commands.types.TempdeckDeactivateMessage, opentrons.legacy_commands.types.MagdeckEngageMessage, opentrons.legacy_commands.types.MagdeckDisengageMessage, opentrons.legacy_commands.types.MagdeckCalibrateMessage, opentrons.legacy_commands.types.CommentMessage, opentrons.legacy_commands.types.DelayMessage, opentrons.legacy_commands.types.PauseMessage, opentrons.legacy_commands.types.ResumeMessage, opentrons.legacy_commands.types.MoveToMessage, opentrons.legacy_commands.types.MoveToDisposalLocationMessage, opentrons.legacy_commands.types.MoveLabwareMessage]], NoneType]] \= None_, _custom_labware_paths: Optional\[List\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')]] \= None_, _custom_data_paths: Optional\[List\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')]] \= None_) → [None](https://docs.python.org/3/library/constants.html#None '(in Python v3.12)') +Run the protocol itself. + +This is a one\-stop function to run a protocol, whether python or json, +no matter the api version, from external (i.e. not bound up in other +internal server infrastructure) sources. + +To run an opentrons protocol from other places, pass in a file like +object as protocol_file; this function either returns (if the run has no +problems) or raises an exception. + +To call from the command line use either the autogenerated entrypoint +`opentrons_execute` or `python -m opentrons.execute`. + +Parameters: + +- **protocol_file** – The protocol file to execute +- **protocol_name** – The name of the protocol file. This is required + internally, but it may not be a thing we can get + from the protocol_file argument. +- **propagate_logs** – Whether this function should allow logs from the + Opentrons stack to propagate up to the root handler. + This can be useful if you’re integrating this + function in a larger application, but most logs that + occur during protocol simulation are best associated + with the actions in the protocol that cause them. + Default: `False` +- **log_level** – The level of logs to emit on the command line: + `"debug"`, `"info"`, `"warning"`, or `"error"`. + Defaults to `"warning"`. +- **emit_runlog** – A callback for printing the run log. If specified, this + will be called whenever a command adds an entry to the + run log, which can be used for display and progress + estimation. If specified, the callback should take a + single argument (the name doesn’t matter) which will + be a dictionary: + +``` +{ + 'name': command_name, + 'payload': { + 'text': string_command_text, + # The rest of this struct is + # command-dependent; see + # opentrons.legacy_commands.commands. + } +} + +``` + +Note + +In older software versions, `payload["text"]` was a +[format string](https://docs.python.org/3/library/string.html#formatstrings). +To get human\-readable text, you had to do `payload["text"].format(**payload)`. +Don’t do that anymore. If `payload["text"]` happens to contain any +`{` or `}` characters, it can confuse `.format()` and cause it to raise a +`KeyError`. + +- **custom_labware_paths** – A list of directories to search for custom labware. + Loads valid labware from these paths and makes them available + to the protocol context. If this is `None` (the default), and + this function is called on a robot, it will look in the `labware` + subdirectory of the Jupyter data directory. +- **custom_data_paths** – A list of directories or files to load custom + data files from. Ignored if the apiv2 feature + flag if not set. Entries may be either files or + directories. Specified files and the + non\-recursive contents of specified directories + are presented by the protocol context in + `ProtocolContext.bundled_data`. + +opentrons.execute.get*arguments(\_parser: [argparse.ArgumentParser](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser '(in Python v3.12)')*) → [argparse.ArgumentParser](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser '(in Python v3.12)') +Get the argument parser for this module + +Useful if you want to use this module as a component of another CLI program +and want to add its arguments. + +Parameters: +**parser** – A parser to add arguments to. + +Returns argparse.ArgumentParser: +The parser with arguments added. + +opentrons.execute.get*protocol_api(\_version: Union\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)'), opentrons.protocols.api_support.types.APIVersion]*, _bundled_labware: Optional\[Dict\[str, ForwardRef('LabwareDefinitionDict')]] \= None_, _bundled_data: Optional\[Dict\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)'), [bytes](https://docs.python.org/3/library/stdtypes.html#bytes '(in Python v3.12)')]] \= None_, _extra_labware: Optional\[Dict\[str, ForwardRef('LabwareDefinitionDict')]] \= None_) → [opentrons.protocol_api.protocol_context.ProtocolContext](index.html#opentrons.protocol_api.ProtocolContext 'opentrons.protocol_api.protocol_context.ProtocolContext') +Build and return a `protocol_api.ProtocolContext` +connected to the robot. + +This can be used to run protocols from interactive Python sessions +such as Jupyter or an interpreter on the command line: + +``` +>>> from opentrons.execute import get_protocol_api +>>> protocol = get_protocol_api('2.0') +>>> instr = protocol.load_instrument('p300_single', 'right') +>>> instr.home() + +``` + +When this function is called, modules and instruments will be recached. + +Parameters: + +- **version** – The API version to use. This must be lower than + `opentrons.protocol_api.MAX_SUPPORTED_VERSION`. + It may be specified either as a string (`'2.0'`) or + as a `protocols.types.APIVersion` + (`APIVersion(2, 0)`). +- **bundled_labware** – If specified, a mapping from labware names to + labware definitions for labware to consider in the + protocol. Note that if you specify this, \_only\_ + labware in this argument will be allowed in the + protocol. This is preparation for a beta feature + and is best not used. +- **bundled_data** – If specified, a mapping from filenames to contents + for data to be available in the protocol from + [`opentrons.protocol_api.ProtocolContext.bundled_data`](#opentrons.protocol_api.ProtocolContext.bundled_data 'opentrons.protocol_api.ProtocolContext.bundled_data'). +- **extra_labware** – A mapping from labware load names to custom labware definitions. + If this is `None` (the default), and this function is called on a robot, + it will look for labware in the `labware` subdirectory of the Jupyter + data directory. + +Returns: +The protocol context. + +opentrons.execute.main() → [int](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)') +Handler for command line invocation to run a protocol. + +Parameters: +**argv** – The arguments the program was invoked with; this is usually +[`sys.argv`](https://docs.python.org/3/library/sys.html#sys.argv '(in Python v3.12)') but if you want to override that you can. + +Returns int: +A success or failure value suitable for use as a shell +return code passed to [`sys.exit`](https://docs.python.org/3/library/sys.html#sys.exit '(in Python v3.12)') (0 means success, +anything else is a kind of failure). + +opentrons.simulate: functions and entrypoints for simulating protocols + +This module has functions that provide a console entrypoint for simulating +a protocol from the command line. + +opentrons.simulate.allow_bundle() → [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)') +Check if bundling is allowed with a special not\-exposed\-to\-the\-app flag. + +Returns `True` if the environment variable +`OT_API_FF_allowBundleCreation` is `"1"` + +opentrons.simulate.bundle*from_sim(\_protocol: opentrons.protocols.types.PythonProtocol*, _context: [opentrons.protocol_api.protocol_context.ProtocolContext](index.html#opentrons.protocol_api.ProtocolContext 'opentrons.protocol_api.protocol_context.ProtocolContext')_) → opentrons.protocols.types.BundleContents +From a protocol, and the context that has finished simulating that +protocol, determine what needs to go in a bundle for the protocol. + +opentrons.simulate.format*runlog(\_runlog: List\[Mapping\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)'), Any]]*) → [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)') +Format a run log (return value of [`simulate`](#opentrons.simulate.simulate 'opentrons.simulate.simulate')) into a +human\-readable string + +Parameters: +**runlog** – The output of a call to [`simulate`](#opentrons.simulate.simulate 'opentrons.simulate.simulate') + +opentrons.simulate.get*arguments(\_parser: [argparse.ArgumentParser](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser '(in Python v3.12)')*) → [argparse.ArgumentParser](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser '(in Python v3.12)') +Get the argument parser for this module + +Useful if you want to use this module as a component of another CLI program +and want to add its arguments. + +Parameters: +**parser** – A parser to add arguments to. If not specified, one will be +created. + +Returns argparse.ArgumentParser: +The parser with arguments added. + +opentrons.simulate.get*protocol_api(\_version: Union\[str, opentrons.protocols.api_support.types.APIVersion], bundled_labware: Optional\[Dict\[str, ForwardRef('LabwareDefinitionDict')]] \= None, bundled_data: Optional\[Dict\[str, bytes]] \= None, extra_labware: Optional\[Dict\[str, ForwardRef('LabwareDefinitionDict')]] \= None, hardware_simulator: Optional\[opentrons.hardware_control.thread_manager.ThreadManager\[Union\[opentrons.hardware_control.protocols.HardwareControlInterface\[opentrons.hardware_control.robot_calibration.RobotCalibration, opentrons.types.Mount, opentrons.config.types.RobotConfig], opentrons.hardware_control.protocols.FlexHardwareControlInterface\[opentrons.hardware_control.ot3_calibration.OT3Transforms, Union\[opentrons.types.Mount, opentrons.hardware_control.types.OT3Mount], opentrons.config.types.OT3Config]]]] \= None, \\\*, robot_type: Optional\[Literal\['OT\-2', 'Flex']] \= None, use_virtual_hardware: bool \= True*) → [opentrons.protocol_api.protocol_context.ProtocolContext](index.html#opentrons.protocol_api.ProtocolContext 'opentrons.protocol_api.protocol_context.ProtocolContext') +Build and return a `protocol_api.ProtocolContext` +connected to Virtual Smoothie. + +This can be used to run protocols from interactive Python sessions +such as Jupyter or an interpreter on the command line: + +``` +>>> from opentrons.simulate import get_protocol_api +>>> protocol = get_protocol_api('2.0') +>>> instr = protocol.load_instrument('p300_single', 'right') +>>> instr.home() + +``` + +Parameters: + +- **version** – The API version to use. This must be lower than + `opentrons.protocol_api.MAX_SUPPORTED_VERSION`. + It may be specified either as a string (`'2.0'`) or + as a `protocols.types.APIVersion` + (`APIVersion(2, 0)`). +- **bundled_labware** – If specified, a mapping from labware names to + labware definitions for labware to consider in the + protocol. Note that if you specify this, \_only\_ + labware in this argument will be allowed in the + protocol. This is preparation for a beta feature + and is best not used. +- **bundled_data** – If specified, a mapping from filenames to contents + for data to be available in the protocol from + [`opentrons.protocol_api.ProtocolContext.bundled_data`](#opentrons.protocol_api.ProtocolContext.bundled_data 'opentrons.protocol_api.ProtocolContext.bundled_data'). +- **extra_labware** – A mapping from labware load names to custom labware definitions. + If this is `None` (the default), and this function is called on a robot, + it will look for labware in the `labware` subdirectory of the Jupyter + data directory. +- **hardware_simulator** – If specified, a hardware simulator instance. +- **robot_type** – The type of robot to simulate: either `"Flex"` or `"OT-2"`. + If you’re running this function on a robot, the default is the type of that + robot. Otherwise, the default is `"OT-2"`, for backwards compatibility. +- **use_virtual_hardware** – If true, use the protocol engines virtual hardware, if false use the lower level hardware simulator. + +Returns: +The protocol context. + +opentrons.simulate.main() → [int](https://docs.python.org/3/library/functions.html#int '(in Python v3.12)') +Run the simulation + +opentrons.simulate.simulate(_protocol_file: Union\[BinaryIO, TextIO]_, _file_name: Optional\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')] \= None_, _custom_labware_paths: Optional\[List\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')]] \= None_, _custom_data_paths: Optional\[List\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')]] \= None_, _propagate_logs: [bool](https://docs.python.org/3/library/functions.html#bool '(in Python v3.12)') \= False_, _hardware_simulator_file_path: Optional\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)')] \= None_, _duration_estimator: Optional\[opentrons.protocols.duration.estimator.DurationEstimator] \= None_, _log_level: [str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)') \= 'warning'_) → Tuple\[List\[Mapping\[[str](https://docs.python.org/3/library/stdtypes.html#str '(in Python v3.12)'), Any]], Optional\[opentrons.protocols.types.BundleContents]] +Simulate the protocol itself. + +This is a one\-stop function to simulate a protocol, whether python or json, +no matter the api version, from external (i.e. not bound up in other +internal server infrastructure) sources. + +To simulate an opentrons protocol from other places, pass in a file like +object as protocol_file; this function either returns (if the simulation +has no problems) or raises an exception. + +To call from the command line use either the autogenerated entrypoint +`opentrons_simulate` (`opentrons_simulate.exe`, on windows) or +`python -m opentrons.simulate`. + +The return value is the run log, a list of dicts that represent the +commands executed by the robot; and either the contents of the protocol +that would be required to bundle, or `None`. + +Each dict element in the run log has the following keys: + +> - `level`: The depth at which this command is nested. If this an +> aspirate inside a mix inside a transfer, for instance, it would be 3\. +> - `payload`: The command. The human\-readable run log text is available at +> `payload["text"]`. The other keys of `payload` are command\-dependent; +> see `opentrons.legacy_commands`. +> +> Note +> +> In older software versions, `payload["text"]` was a +> [format string](https://docs.python.org/3/library/string.html#formatstrings). +> To get human\-readable text, you had to do `payload["text"].format(**payload)`. +> Don’t do that anymore. If `payload["text"]` happens to contain any +> `{` or `}` characters, it can confuse `.format()` and cause it to raise a +> `KeyError`. +> +> - `logs`: Any log messages that occurred during execution of this +> command, as a standard Python [`LogRecord`](https://docs.python.org/3/library/logging.html#logging.LogRecord '(in Python v3.12)'). + +Parameters: + +- **protocol_file** – The protocol file to simulate. +- **file_name** – The name of the file +- **custom_labware_paths** – A list of directories to search for custom labware. + Loads valid labware from these paths and makes them available + to the protocol context. If this is `None` (the default), and + this function is called on a robot, it will look in the `labware` + subdirectory of the Jupyter data directory. +- **custom_data_paths** – A list of directories or files to load custom + data files from. Ignored if the apiv2 feature + flag if not set. Entries may be either files or + directories. Specified files and the + non\-recursive contents of specified directories + are presented by the protocol context in + `protocol_api.ProtocolContext.bundled_data`. +- **hardware_simulator_file_path** – A path to a JSON file defining a + hardware simulator. +- **duration_estimator** – For internal use only. + Optional duration estimator object. +- **propagate_logs** – Whether this function should allow logs from the + Opentrons stack to propagate up to the root handler. + This can be useful if you’re integrating this + function in a larger application, but most logs that + occur during protocol simulation are best associated + with the actions in the protocol that cause them. + Default: `False` +- **log_level** – The level of logs to capture in the run log: + `"debug"`, `"info"`, `"warning"`, or `"error"`. + Defaults to `"warning"`. + +Returns: +A tuple of a run log for user output, and possibly the required +data to write to a bundle to bundle this protocol. The bundle is +only emitted if bundling is allowed +and this is an unbundled Protocol API +v2 python protocol. In other cases it is None. diff --git a/opentrons-ai-server/api/utils/convert_to_markdown.py b/opentrons-ai-server/api/utils/convert_to_markdown.py new file mode 100644 index 00000000000..cacd23d2558 --- /dev/null +++ b/opentrons-ai-server/api/utils/convert_to_markdown.py @@ -0,0 +1,214 @@ +import os.path +import subprocess +import uuid + +from bs4 import BeautifulSoup +from bs4.element import Tag +from markdownify import markdownify # type: ignore + + +def run_sphinx_build(command: str) -> None: + """Run the sphinx command to convert rst files to a single HTML file.""" + try: + subprocess.run(command, check=True, shell=True) + except subprocess.CalledProcessError as e: + print(f"An error occurred while running Sphinx build: {e}") + + +def remove_specific_logos(soup: BeautifulSoup) -> BeautifulSoup: + """Remove specific logos from the HTML.""" + logos = soup.find_all("img", src=lambda x: x and ("opentrons-images/website" in x)) + for logo in logos: + logo.decompose() + return soup + + +def remove_all_images(soup: BeautifulSoup) -> BeautifulSoup: + """Remove all images from the HTML.""" + all_images = soup.find_all("img") + for img in all_images: + img.decompose() + return soup + + +def remove_pilcrow_symbols(soup: BeautifulSoup) -> BeautifulSoup: + """Remove all pilcrow symbols from the HTML.""" + pilcrow_symbols = soup.find_all("a", string="¶") + for symbol in pilcrow_symbols: + symbol.decompose() + return soup + + +def remove_list_items_containing_ot1(soup: BeautifulSoup) -> BeautifulSoup: + """Remove all
  • elements containing 'OT-1'.""" + list_items = soup.find_all("li") + for li in list_items: + if "OT-1" in li.get_text(): + li.decompose() + return soup + + +def remove_top_section(soup: BeautifulSoup) -> BeautifulSoup: + """Remove everything before a Python API docs header section.""" + # Remove everything before the
    element + start_section = soup.find("div", class_="document") + + # Check if the section was found + if not start_section: + print("Start section not found in the HTML content.") + return soup + + # Find the head tag and remove it + head_tag = soup.find("head") + if isinstance(head_tag, Tag): + head_tag.decompose() + + # Remove all previous siblings of the start_section + for previous in list(start_section.previous_siblings): + previous.extract() + + # Remove the parent elements if they are no longer needed + for parent in list(start_section.parents): + if parent.name == "body": + break + if not parent.find_previous_siblings() and not parent.find_next_siblings(): + parent.extract() + + return soup + + +def remove_footer_content(soup: BeautifulSoup) -> BeautifulSoup: + """Remove the footer content from the HTML.""" + footer_section = soup.find("footer") + if isinstance(footer_section, Tag): + footer_section.decompose() + return soup + + +def clean_html(soup: BeautifulSoup) -> BeautifulSoup: + """Clean up the unused features in the HTML file.""" + soup = remove_specific_logos(soup) + soup = remove_all_images(soup) + soup = remove_pilcrow_symbols(soup) + soup = remove_list_items_containing_ot1(soup) + soup = remove_top_section(soup) + soup = remove_footer_content(soup) + return soup + + +def extract_and_remove_api_reference(html_file_path: str, output_file_path: str) -> BeautifulSoup: + """Extract and remove the API Version 2 Reference section and write it to a Markdown file.""" + + with open(html_file_path, "r", encoding="utf-8") as file: + html_content = file.read() + + soup = BeautifulSoup(html_content, "html.parser") + soup = clean_html(soup) + + # Find the start and end points + start_span = soup.find("span", id="document-new_protocol_api") + if start_span is None: + print("Start span not found.") + return soup + + # Get the section to keep + api_section = start_span.find_next_sibling("section", id="api-version-2-reference") + if api_section is None: + print("API section not found.") + return soup + + # Create a BeautifulSoup object for the extracted section + extracted_html = str(start_span) + str(api_section) + reference_markdown = markdownify(extracted_html) + + # Write the extracted content to a Markdown file + with open(output_file_path, "w", encoding="utf-8") as file: + file.write(reference_markdown) + + # Remove it from the main markdown file + if isinstance(start_span, Tag) and isinstance(api_section, Tag): + start_span.decompose() + api_section.decompose() + + return soup + + +def extract_tab_content(soup: BeautifulSoup) -> tuple[BeautifulSoup, dict[str, str]]: + """Find all tabbed content sections and convert each tabbed section to markdown format.""" + tab_sections = soup.find_all(class_="sphinx-tabs docutils container") + tab_markdown = {} + + for _idx, tab_section in enumerate(tab_sections): + tab_buttons = tab_section.find_all(class_="sphinx-tabs-tab") + tab_panels = tab_section.find_all(class_="sphinx-tabs-panel") + + section_markdown = [] + for button, panel in zip(tab_buttons, tab_panels, strict=False): + section_markdown.append(f"### {button.text.strip()}\n") + panel_content = markdownify(str(panel), strip=["div"]) + section_markdown.append(panel_content) + combined_section_markdown = "\n".join(section_markdown) + "\n\n" + # Replace the original tab section with an unique placeholder in the soup + placeholder = f"tabSectionIs{uuid.uuid4().hex}" + tab_markdown[placeholder] = combined_section_markdown + placeholder_tag = soup.new_tag("div") + placeholder_tag.string = placeholder + tab_section.replace_with(placeholder_tag) + + return soup, tab_markdown + + +def convert_html_to_markdown(html_file_path: str, markdown_file_path: str, reference_file_path: str) -> None: + """Converts an HTML file to a Markdown file with specific modifications.""" + + soup = extract_and_remove_api_reference(html_file_path, reference_file_path) + soup, tab_markdown = extract_tab_content(soup) + + modified_html_content = str(soup) + full_markdown = markdownify(modified_html_content) + + for placeholder, section_md in tab_markdown.items(): + if placeholder in full_markdown: + full_markdown = full_markdown.replace(placeholder, section_md) + + with open(markdown_file_path, "w", encoding="utf-8") as file: + file.write(full_markdown) + + +def get_latest_version() -> str: + """Get the lastest docs version number.""" + try: + # Run the git command to get the latest tag + command = "git tag -l 'docs@2*' --sort=-taggerdate | head -n 1" + result = subprocess.run(command, capture_output=True, text=True, shell=True) + # Extract the tag from the output and remove '.' + tag = "".join(result.stdout.strip().split(".")) + + version = tag.split("@")[1] + version = version.split("_")[0] + return version + except subprocess.CalledProcessError as e: + print(f"An error occurred while getting the version: {e}") + return "" + + +def get_markdown_format() -> None: + """Generates a version-aware Markdown file from HTML documentation.""" + current_version = get_latest_version() + current_dir = os.path.dirname(__file__) + + docs_src_path = os.path.join("..", "api", "docs", "v2") + build_html_path = os.path.join(current_dir, "build", "docs", "html", "v2") + html_file_path = os.path.join(build_html_path, "index.html") + markdown_file_path = os.path.join(current_dir, "..", "data", f"python_api_{current_version}_docs.md") + reference_file_path = os.path.join(current_dir, "..", "data", f"python_api_{current_version}_reference.md") + + command = f"pipenv run sphinx-build -b singlehtml {docs_src_path} {build_html_path}" + + run_sphinx_build(command) + + convert_html_to_markdown(html_file_path, markdown_file_path, reference_file_path) + + +if __name__ == "__main__": + get_markdown_format() diff --git a/opentrons-ai-server/tests/test_convert_to_markdown.py b/opentrons-ai-server/tests/test_convert_to_markdown.py new file mode 100644 index 00000000000..8210ccf4f3f --- /dev/null +++ b/opentrons-ai-server/tests/test_convert_to_markdown.py @@ -0,0 +1,145 @@ +from unittest.mock import MagicMock, mock_open, patch + +import pytest +from api.utils.convert_to_markdown import ( + clean_html, + convert_html_to_markdown, + extract_and_remove_api_reference, + extract_tab_content, + get_latest_version, + get_markdown_format, + remove_all_images, + remove_footer_content, + remove_list_items_containing_ot1, + remove_pilcrow_symbols, + remove_specific_logos, + remove_top_section, + run_sphinx_build, +) +from bs4 import BeautifulSoup + +# Sample HTML content for testing +sample_html = """ + + + +
    +
    + + +
  • OT-1
  • + +
    +
    +
    Tab 1
    +
    Content 1
    +
    Tab 2
    +
    Content 2
    +
    + + +""" + + +@pytest.fixture +def soup() -> BeautifulSoup: + return BeautifulSoup(sample_html, "html.parser") + + +@pytest.mark.unit +def test_run_sphinx_build() -> None: + with patch("subprocess.run") as mock_run: + run_sphinx_build("echo test") + mock_run.assert_called_once_with("echo test", check=True, shell=True) + + +@pytest.mark.unit +def test_remove_specific_logos(soup: BeautifulSoup) -> None: + soup = remove_specific_logos(soup) + assert not soup.find_all("img", src="opentrons-images/website/logo.png") + + +@pytest.mark.unit +def test_remove_all_images(soup: BeautifulSoup) -> None: + soup = remove_all_images(soup) + assert not soup.find_all("img") + + +@pytest.mark.unit +def test_remove_pilcrow_symbols(soup: BeautifulSoup) -> None: + soup = remove_pilcrow_symbols(soup) + assert not soup.find_all("a", string="¶") + + +@pytest.mark.unit +def test_remove_list_items_containing_ot1(soup: BeautifulSoup) -> None: + soup = remove_list_items_containing_ot1(soup) + assert not soup.find_all("li", string="OT-1") + + +@pytest.mark.unit +def test_remove_top_section(soup: BeautifulSoup) -> None: + soup = remove_top_section(soup) + assert not soup.find("head") + + +@pytest.mark.unit +def test_remove_footer_content(soup: BeautifulSoup) -> None: + soup = remove_footer_content(soup) + assert not soup.find("footer") + + +@pytest.mark.unit +def test_clean_html(soup: BeautifulSoup) -> None: + soup = clean_html(soup) + assert not soup.find_all("img", src="opentrons-images/website/logo.png") + assert not soup.find_all("img") + assert not soup.find_all("a", string="¶") + assert not soup.find_all("li", string="OT-1") + assert not soup.find("head") + assert not soup.find("footer") + + +@pytest.mark.unit +@patch("builtins.open", new_callable=mock_open, read_data=sample_html) +def test_extract_and_remove_api_reference(mock_file: MagicMock, soup: BeautifulSoup) -> None: + output_file_path = "output.md" + html_file_path = "index.html" + soup = extract_and_remove_api_reference(html_file_path, output_file_path) + assert not soup.find("span", id="document-new_protocol_api") + assert not soup.find("section", id="api-version-2-reference") + + +@pytest.mark.unit +def test_extract_tab_content(soup: BeautifulSoup) -> None: + soup, tab_markdown = extract_tab_content(soup) + assert len(tab_markdown) == 1 + + +@pytest.mark.unit +@patch("builtins.open", new_callable=mock_open) +def test_convert_html_to_markdown(mock_file: MagicMock, soup: BeautifulSoup) -> None: + html_file_path = "index.html" + markdown_file_path = "output.md" + reference_file_path = "reference.md" + convert_html_to_markdown(html_file_path, markdown_file_path, reference_file_path) + mock_file.assert_called() + + +@pytest.mark.unit +@patch("subprocess.run") +def test_get_latest_version(mock_run: MagicMock) -> None: + mock_run.return_value.stdout = "docs@2.19_2\n" + version = get_latest_version() + assert version == "219" + + +@pytest.mark.unit +@patch("api.utils.convert_to_markdown.get_latest_version") +@patch("api.utils.convert_to_markdown.run_sphinx_build") +@patch("api.utils.convert_to_markdown.convert_html_to_markdown") +def test_get_markdown_format(mock_convert: MagicMock, mock_build: MagicMock, mock_version: MagicMock) -> None: + mock_version.return_value = "200" + get_markdown_format() + mock_build.assert_called() + mock_convert.assert_called()