Skip to content

Commit

Permalink
Merge branch 'edge' into ethanfjones-patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
Laura-Danielle authored May 27, 2021
2 parents d92a18e + 9733697 commit 05a4513
Show file tree
Hide file tree
Showing 57 changed files with 1,193 additions and 296 deletions.
38 changes: 38 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
./api-server
./app
./labware-library
./node_modules
./protocol-designer
./discovery-client
./coverage
./components
./app-shell
./update-server
./step-generation

.pytest_cache
.flake8
.idea
.pypy_cache
dist
build
yarn.lock
Pipfile.lock

__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
pip-log.txt
pip-delete-this-directory.txt
.tox
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
*.log
.git
18 changes: 18 additions & 0 deletions .opentrons_config/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"labware_database_file": "/config/opentrons.db",
"labware_calibration_offsets_dir_v2": "/config/labware/v2/offsets",
"labware_user_definitions_dir_v2": "/config/labware/v2/custom_definitions",
"feature_flags_file": "/config/feature_flags.json",
"robot_settings_file": "/config/robot_settings.json",
"deck_calibration_file": "/config/deck_calibration.json",
"log_dir": "/config/logs",
"api_log_file": "/config/logs/api.log",
"serial_log_file": "/config/logs/serial.log",
"wifi_keys_dir": "/config/user_storage/opentrons_data/network_keys",
"hardware_controller_lockfile": "/config/hardware.lock",
"pipette_config_overrides_dir": "/config/pipettes",
"tip_length_calibration_dir": "/config/tip_lengths",
"robot_calibration_dir": "/config/robot",
"pipette_calibration_dir": "/config/robot/pipettes",
"custom_tiprack_dir": "/config/tip_lengths/custom_tiprack_definitions"
}
17 changes: 17 additions & 0 deletions .opentrons_config/feature_flags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"shortFixedTrash": null,
"calibrateToBottom": null,
"deckCalibrationDots": null,
"useProtocolApi2": null,
"disableHomeOnBoot": null,
"useOldAspirationFunctions": null,
"enableDoorSafetySwitch": null,
"enableTipLengthCalibration": null,
"enableHttpProtocolSessions": null,
"enableFastProtocolUpload": null,
"enableProtocolEngine": null,
"disableLogAggregation": null,
"enableApi1BackCompat": null,
"useV1HttpApi": null,
"_version": 9
}
1 change: 1 addition & 0 deletions .opentrons_config/pipettes/P20SV202020070101.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "model": "p20_single_v2.0" }
1 change: 1 addition & 0 deletions .opentrons_config/pipettes/P3HMV202020041605.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "model": "p20_multi_v2.0" }
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ Your computer will need the following tools installed to be able to develop with
pyenv install 3.7.6
```

**MacOS Big Sur Note:** due to this [known issue](https://github.com/pyenv/pyenv/issues/1737) we recommend using:

```shell
pyenv install 3.7.10
```

- Node v12 - [nvm][] is optional, but recommended

```shell
Expand Down
28 changes: 28 additions & 0 deletions DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Docker Guide
=======================
Included in this repo are the tools to run a containerized Opentrons robot stack in docker.

This includes the `robot-server` connected to the hardware emulation application. The emulation application includes the Smoothie and magnetic, temperature, and thermocycler modules.

## Requirements

- A clone of this repo.
- An installation [docker](https://docs.docker.com/get-docker/)
- An installation of [docker-compose](https://docs.docker.com/compose/install/)

## How to use

Start a terminal and change directory to the root of this repo.

1. Build
Enter `docker-compose build --force-rm` at the terminal.

2. Run
Enter `docker-compose up` at the terminal. _The build and run stages can be combined `docker-compose up --build`._

3. Start the Opentrons application. The docker container will appear as `dev`. Connect and run just as you would on a robot.

## Known Issues

- Pipettes cannot be changed at run time.
- Pipettes are fixed as `p20_multi_v2.0` on the left mount and `p20_single_v2.0` on the right.
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM ubuntu as base
RUN apt-get update && apt-get install -y python3 pip

FROM base as builder
COPY scripts scripts
COPY LICENSE LICENSE

COPY shared-data shared-data

COPY api/MANIFEST.in api/MANIFEST.in
COPY api/setup.py api/setup.py
COPY api/pypi-readme.rst api/pypi-readme.rst
COPY api/src/opentrons api/src/opentrons

COPY notify-server/setup.py notify-server/setup.py
COPY notify-server/README.rst notify-server/README.rst
COPY notify-server/notify_server notify-server/notify_server

COPY robot-server/setup.py robot-server/setup.py
COPY robot-server/robot_server robot-server/robot_server

RUN cd shared-data/python && python3 setup.py bdist_wheel -d /dist/
RUN cd api && python3 setup.py bdist_wheel -d /dist/
RUN cd notify-server && python3 setup.py bdist_wheel -d /dist/
RUN cd robot-server && python3 setup.py bdist_wheel -d /dist/

FROM base
COPY --from=builder /dist /dist
RUN pip install /dist/*
1 change: 0 additions & 1 deletion api/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
include src/opentrons/package.json
graft src/opentrons/config
graft src/opentrons/resources
include src/opentrons/server/openapi.json
exclude pyproject.toml
4 changes: 2 additions & 2 deletions api/docs/v2/new_atomic_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ The examples in this section should be inserted in the following:
metadata = {'apiLevel': '|apiLevel|'}
def run(protocol):
tiprack = protocol.load_labware('corning_96_wellplate_360ul_flat', 2)
plate = protocol.load_labware('opentrons_96_tiprack_300ul', 3)
plate = protocol.load_labware('corning_96_wellplate_360ul_flat', 2)
tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', 3)
pipette = protocol.load_instrument('p300_single_gen2', mount='left', tip_racks=[tiprack])
pipette.pick_up_tip()
# example code goes here
Expand Down
1 change: 1 addition & 0 deletions api/docs/v2/new_examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ This accomplishes the same thing as the following basic commands:
p300.aspirate(100, plate['A1'])
p300.dispense(100, plate['B1'])
p300.return_tip()
p300.drop_tip()
******************************

Expand Down
17 changes: 8 additions & 9 deletions api/src/opentrons/hardware_control/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,17 +269,16 @@ def disengage_axes(self, axes: List[str]):
self._smoothie_driver.disengage_axis(''.join(axes))

def set_lights(self, button: Optional[bool], rails: Optional[bool]):
if opentrons.config.IS_ROBOT:
if button is not None:
self.gpio_chardev.set_button_light(blue=button)
if rails is not None:
self.gpio_chardev.set_rail_lights(rails)
if button is not None:
self.gpio_chardev.set_button_light(blue=button)
if rails is not None:
self.gpio_chardev.set_rail_lights(rails)

def get_lights(self) -> Dict[str, bool]:
if not opentrons.config.IS_ROBOT:
return {}
return {'button': self.gpio_chardev.get_button_light()[2],
'rails': self.gpio_chardev.get_rail_lights()}
return {
'button': self.gpio_chardev.get_button_light()[2],
'rails': self.gpio_chardev.get_rail_lights()
}

def pause(self):
self._smoothie_driver.pause()
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/hardware_control/emulation/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async def run_server(host: str, port: int, handler: ConnectionHandler) -> None:

async def run() -> None:
"""Run the module emulators."""
host = "127.0.0.1"
host = "0.0.0.0"

await asyncio.gather(
run_server(host=host,
Expand Down
27 changes: 27 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '3'
services:
emulator:
build: .
command: python3 -m opentrons.hardware_control.emulation.app
ports:
- '9996:9996'
- '9997:9997'
- '9998:9998'
- '9999:9999'
robot-server:
build: .
command: uvicorn "robot_server:app" --host 0.0.0.0 --port 31950 --ws wsproto --reload
ports:
- '31950:31950'
environment:
- OT_API_CONFIG_DIR=/config
- OT_SMOOTHIE_EMULATOR_URI=socket://emulator:9996
- OT_THERMOCYCLER_EMULATOR_URI=socket://emulator:9997
- OT_TEMPERATURE_EMULATOR_URI=socket://emulator:9998
- OT_MAGNETIC_EMULATOR_URI=socket://emulator:9999
links:
- 'emulator'
depends_on:
- 'emulator'
volumes:
- .opentrons_config:/config:rw
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,14 @@ context('Reservoirs', () => {
it('tests number of rows', () => {
cy.get("input[name='gridRows']").focus().blur()
cy.contains('Number of rows must be a number').should('exist')
cy.get("input[name='gridRows']").type('10').blur()
cy.get("input[name='gridRows']").type('1').blur()
cy.contains('Number of rows must be a number').should('not.exist')
})

it('tests are all of your rows evenly spaced', () => {
cy.get("input[name='regularRowSpacing'][value='false']").check({
force: true,
})
cy.contains(
'Your labware is not compatible with the Labware Creator'
).should('exist')
cy.get("input[name='regularRowSpacing'][value='true']").check({
force: true,
})
it('should not ask if all of your rows evenly spaced, since we only have one row', () => {
cy.get("input[name='regularRowSpacing'][value='false']").should(
'not.exist'
)
})

it('tests number of columns', () => {
Expand Down Expand Up @@ -125,7 +119,7 @@ context('Reservoirs', () => {
it('tests volume', () => {
cy.get("input[name='wellVolume']").focus().blur()
cy.contains('Max volume per well must be a number').should('exist')
cy.get("input[name='wellVolume']").type('10').blur()
cy.get("input[name='wellVolume']").type('250').blur()
cy.contains('Max volume per well must be a number').should('not.exist')
})

Expand All @@ -152,11 +146,11 @@ context('Reservoirs', () => {
cy.get("input[name='wellYDimension']").should('exist')
cy.get("input[name='wellXDimension']").focus().blur()
cy.contains('Well X must be a number').should('exist')
cy.get("input[name='wellXDimension']").type('10').blur()
cy.get("input[name='wellXDimension']").type('8').blur()
cy.contains('Well X must be a number').should('not.exist')
cy.get("input[name='wellYDimension']").focus().blur()
cy.contains('Well Y must be a number').should('exist')
cy.get("input[name='wellYDimension']").type('10').blur()
cy.get("input[name='wellYDimension']").type('60').blur()
cy.contains('Well Y must be a number').should('not.exist')
})

Expand All @@ -181,19 +175,15 @@ context('Reservoirs', () => {
cy.get("img[src*='_v.']").should('exist')
cy.get("input[name='wellDepth']").focus().blur()
cy.contains('Depth must be a number').should('exist')
cy.get("input[name='wellDepth']").type('10').blur()
cy.get("input[name='wellDepth']").type('70').blur()
cy.contains('Depth must be a number').should('not.exist')
})

it('tests well spacing', () => {
cy.get("input[name='gridSpacingX']").focus().blur()
cy.contains('X Spacing (Xs) must be a number').should('exist')
cy.get("input[name='gridSpacingX']").type('10').blur()
cy.get("input[name='gridSpacingX']").type('12').blur()
cy.contains('X Spacing (Xs) must be a number').should('not.exist')
cy.get("input[name='gridSpacingY']").focus().blur()
cy.contains('Y Spacing (Ys) must be a number').should('exist')
cy.get("input[name='gridSpacingY']").type('10').blur()
cy.contains('Y Spacing (Ys) must be a number').should('not.exist')
})

it('tests grid offset', () => {
Expand All @@ -203,7 +193,7 @@ context('Reservoirs', () => {
cy.contains('X Offset (Xo) must be a number').should('not.exist')
cy.get("input[name='gridOffsetY']").focus().blur()
cy.contains('Y Offset (Yo) must be a number').should('exist')
cy.get("input[name='gridOffsetY']").type('10').blur()
cy.get("input[name='gridOffsetY']").type('45').blur()
cy.contains('Y Offset (Yo) must be a number').should('not.exist')
})

Expand All @@ -228,10 +218,10 @@ context('Reservoirs', () => {
cy.get("input[name='brandId']").type('001')

// File info
cy.get("input[placeholder='TestPro 100 Reservoir 10 µL']").should(
cy.get("input[placeholder='TestPro 10 Reservoir 250 µL']").should(
'exist'
)
cy.get("input[placeholder='testpro_100_reservoir_10ul']").should(
cy.get("input[placeholder='testpro_10_reservoir_250ul']").should(
'exist'
)

Expand Down
Loading

0 comments on commit 05a4513

Please sign in to comment.