import assert from 'assert' ; import { orderWells } from './utils/orderWells' ; import min from 'lodash/min' ; import sortBy from 'lodash/sortBy' ; import { getTiprackVolume , THERMOCYCLER_MODULE_TYPE , } from '@opentrons/shared-data' ; import type { InvariantContext , ModuleTemporalProperties , RobotState , ThermocyclerModuleState , } from './' ; export function sortLabwareBySlot ( labwareState ) { return sortBy < string > ( Object . keys ( labwareState ) , ( id ) => parseInt ( labwareState [ id ] . slot ) ) ; } export function _getNextTip ( args ) { const { pipetteId , tiprackId , invariantContext , robotState } = args ; const pipetteChannels = invariantContext . pipetteEntities [ pipetteId ] ?. spec ?. channels ; const tiprackWellsState = robotState . tipState . tipracks [ tiprackId ] ; const tiprackDef = invariantContext . labwareEntities [ tiprackId ] ?. def ; const hasTip = ( wellName ) : boolean => tiprackWellsState [ wellName ] ; const orderedWells = orderWells ( tiprackDef . ordering , 't2b' , 'l2r' ) ; if ( pipetteChannels === 1 ) { const well = orderedWells . find ( hasTip ) ; return well || null ; } if ( pipetteChannels === 8 ) { const tiprackColumns = tiprackDef . ordering ; const fullColumn = tiprackColumns . find ( col => col . every ( hasTip ) ) ; return fullColumn != null ? fullColumn [ 0 ] : null ; } assert ( false , `Pipette ${ pipetteId } has no channels/spec, cannot _getNextTip` ) ; return null ; } type NextTiprack = { tiprackId : string ; well : string ; } | null ; export function getNextTiprack ( pipetteId , invariantContext , robotState ) { /** Returns the next tiprack that has tips.
      Tipracks are any labwareIds that exist in tipState.tipracks.
      For 8-channel pipette, tipracks need a full column of tips.
      If there are no available tipracks, returns null.
    */ const pipetteEntity = invariantContext . pipetteEntities [ pipetteId ] ; if ( ! pipetteEntity ) { throw new Error ( `cannot getNextTiprack, no pipette entity for pipette "${ pipetteId }"` ) ; } const sortedTipracksIds = sortLabwareBySlot ( robotState . labware ) . filter ( labwareId => { assert ( invariantContext . labwareEntities [ labwareId ] ?. labwareDefURI , `cannot getNextTiprack, no labware entity for "${ labwareId }"` ) ; const isOnDeck = robotState . labware [ labwareId ] . slot != null ; return ( isOnDeck && pipetteEntity . tiprackDefURI === invariantContext . labwareEntities [ labwareId ] ?. labwareDefURI ) ; } ) ; const firstAvailableTiprack = sortedTipracksIds . find ( tiprackId => _getNextTip ( { pipetteId , tiprackId , invariantContext , robotState , } ) ) ; const nextTip = firstAvailableTiprack && _getNextTip ( { pipetteId , tiprackId : firstAvailableTiprack , invariantContext , robotState , } ) ; if ( firstAvailableTiprack && nextTip ) { return { tiprackId : firstAvailableTiprack , well : nextTip , } ; } return null ; } export function getPipetteWithTipMaxVol ( pipetteId , invariantContext ) { const pipetteEntity = invariantContext . pipetteEntities [ pipetteId ] ; const pipetteMaxVol = pipetteEntity . spec . maxVolume ; const tiprackDef = pipetteEntity . tiprackLabwareDef ; const tiprackTipVol = getTiprackVolume ( tiprackDef ) ; if ( ! pipetteMaxVol || ! tiprackTipVol ) { assert ( false , `getPipetteEffectiveMaxVol expected tiprackMaxVol and pipette maxVolume to be > 0, got',
      ${ pipetteMaxVol }, ${ tiprackTipVol }` ) ; return NaN ; } return min ( [ tiprackTipVol , pipetteMaxVol ] ) ; } export function getModuleState ( robotState , module ) { if ( ! ( module in robotState . modules ) ) { console . warn ( `getModuleState expected module id "${ module }" to be in robot state` ) ; } return robotState . modules [ module ] ?. moduleState ; } export const thermocyclerStateGetter = ( robotState , moduleId ) : ThermocyclerModuleState | null => { const hardwareModule = robotState . modules [ moduleId ] ?. moduleState ; return hardwareModule && hardwareModule . type === THERMOCYCLER_MODULE_TYPE ? hardwareModule : null ; } ;