import { head } from 'lodash' ; import some from 'lodash/some' ; import uniqBy from 'lodash/uniqBy' ; import { createSelector } from 'reselect' ; import type { ModuleModel , PipetteModel } from '@opentrons/shared-data' ; import { getPipetteModelSpecs } from '@opentrons/shared-data' ; import { getCustomLabwareDefinitions } from '../custom-labware/selectors' ; import { getLabwareDefBySlot } from '../protocol/selectors' ; import { getLatestLabwareDef } from '../../assets/labware/getLabware' ; import * as Constants from './constants' ; import type { State } from '../types' ; import type { Command , CommandNode , ConnectionStatus , Labware , LabwareCalibrationStatus , LabwareType , Mount , NextTiprackPipetteInfo , Pipette , SessionModule , SessionStatus , SessionStatusInfo , Slot , StateLabware , StatePipette , TipracksByMountMap , } from './types' ; import type { ConnectionState } from './reducer/connection' ; import type { CalibrationRequest , CalibrationState , } from './reducer/calibration' ; import type { Request , SessionState } from './reducer/session' ; const calibration = ( state ) : CalibrationState => state . robot . calibration ; const connection = ( state ) : ConnectionState => state . robot . connection ; const session = ( state ) : SessionState => state . robot . session ; const sessionRequest = ( state ) : Request => session ( state ) . sessionRequest ; const cancelRequest = ( state ) : Request => session ( state ) . cancelRequest ; export function isMount ( target ) { return Constants . PIPETTE_MOUNTS . includes ( target as Mount ) ; } export function isSlot ( target ) { return Constants . DECK_SLOTS . includes ( target as Slot ) ; } export function labwareType ( labware ) { return labware . isTiprack ? 'tiprack' : 'labware' ; } export function getConnectRequest ( state ) { return connection ( state ) . connectRequest ; } export function getConnectedRobotName ( state ) { return connection ( state ) . connectedTo || null ; } export const getConnectionStatus = createSelector ( getConnectedRobotName , state => getConnectRequest ( state ) . inProgress , state => connection ( state ) . disconnectRequest . inProgress , state => connection ( state ) . unexpectedDisconnect , ( connectedTo , isConnecting , isDisconnecting , unexpectedDisconnect ) => { if ( unexpectedDisconnect ) return Constants . DISCONNECTED ; if ( ! connectedTo && isConnecting ) return Constants . CONNECTING ; if ( connectedTo && ! isDisconnecting ) return Constants . CONNECTED ; if ( connectedTo && isDisconnecting ) return Constants . DISCONNECTING ; return Constants . DISCONNECTED ; } ) ; export function getSessionCapabilities ( state ) { return session ( state ) . capabilities ; } export function getSessionLoadInProgress ( state ) { return sessionRequest ( state ) . inProgress ; } export function getUploadError ( state ) { return sessionRequest ( state ) . error ; } export function getSessionStatus ( state ) { return session ( state ) . state ; } export function getSessionStatusInfo ( state ) { return session ( state ) . statusInfo ; } export function getSessionIsLoaded ( state ) { return getSessionStatus ( state ) !== ( '' as SessionStatus ) ; } export function getIsReadyToRun ( state ) { return getSessionStatus ( state ) === ( 'loaded' as SessionStatus ) ; } export function getIsRunning ( state ) { const status = getSessionStatus ( state ) ; return ( status === ( 'running' as SessionStatus ) || status === ( 'paused' as SessionStatus ) ) ; } export function getIsPaused ( state ) { return getSessionStatus ( state ) === ( 'paused' as SessionStatus ) ; } export function getIsBlocked ( state ) { return session ( state ) . blocked ; } export function getCancelInProgress ( state ) { return cancelRequest ( state ) . inProgress ; } export function getIsDone ( state ) { const status = getSessionStatus ( state ) ; return ( status === ( 'error' as SessionStatus ) || status === ( 'finished' as SessionStatus ) || status === ( 'stopped' as SessionStatus ) ) ; } function traverseCommands ( commandsById , parentIsCurrent ) { return function mapIdToCommand ( id , index , commands ) { const { description , handledAt , children } = commandsById [ id ] ; const next = commandsById [ commands [ index + 1 ] ] ; const isCurrent = parentIsCurrent && handledAt != null && ( next == null || next . handledAt == null ) ; const isLast = isCurrent && ! children . length ; return { id , description , handledAt , isCurrent , isLast , children : children . map ( traverseCommands ( commandsById , isCurrent ) ) , } ; } ; } export const getCommands = createSelector ( ( state ) => session ( state ) . protocolCommands , ( state ) => session ( state ) . protocolCommandsById , ( commands , commandsById ) => commands . map ( traverseCommands ( commandsById , true ) ) ) ; export const getRunProgress = createSelector ( getCommands , ( commands ) : number => { const leaves = commands . reduce ( countLeaves , { handled : 0 , total : 0 } ) ; return leaves . total && ( leaves . handled / leaves . total ) * 100 ; function countLeaves ( result , command ) { let { handled , total } = result ; if ( command . children . length ) { return command . children . reduce ( countLeaves , result ) ; } if ( command . handledAt ) handled ++ ; total ++ ; return { handled , total } ; } } ) ; export const getSessionError = createSelector ( ( state ) => session ( state ) . runRequest . error , ( state ) => session ( state ) . errors , ( runError , sessionErrors ) => { if ( runError ) return runError . message ; if ( sessionErrors . length > 0 ) return sessionErrors [ 0 ] . message ; return null ; } ) ; export const getStartTimeMs = ( state ) : number | null => { const { startTime } = session ( state ) ; if ( startTime == null ) { return null ; } return startTime ; } ; function millisToSeconds ( ms ) { return Math . floor ( Math . max ( 0 , ms ) / 1000 ) ; } export function getRunSeconds ( state , now = Date . now ( ) ) { const isRunning = getIsRunning ( state ) ; if ( isRunning ) { const startTimeMs = getStartTimeMs ( state ) ; if ( startTimeMs == null ) { return 0 ; } return millisToSeconds ( now - startTimeMs ) ; } const isDone = getIsDone ( state ) ; if ( isDone ) { const statusInfo = getSessionStatusInfo ( state ) ; if ( statusInfo . changedAt == null ) { return 0 ; } return millisToSeconds ( statusInfo . changedAt ) ; } return 0 ; } /**
 * Same considerations as commented above for `getRunSecondsAt`
 */ export function getPausedSeconds ( state , now = Date . now ( ) ) { const isPaused = getIsPaused ( state ) ; if ( isPaused ) { const startTimeMs = getStartTimeMs ( state ) ; const statusInfo = getSessionStatusInfo ( state ) ; if ( startTimeMs != null && statusInfo . changedAt != null ) { return millisToSeconds ( now - startTimeMs - statusInfo . changedAt ) ; } } return 0 ; } export function getCalibrationRequest ( state ) { return calibration ( state ) . calibrationRequest ; } export function getPipettesByMount ( state ) { return session ( state ) . pipettesByMount ; } export const getPipettes = createSelector ( getPipettesByMount , ( state ) : { [ mount in Mount ] ? : boolean ; } => calibration ( state ) . probedByMount , ( state ) => calibration ( state ) . tipOnByMount , ( pipettesByMount , probedByMount , tipOnByMount ) : Pipette [ ] => { return Constants . PIPETTE_MOUNTS . filter ( ( mount ) => pipettesByMount [ mount ] != null ) . map ( mount => { const pipette = pipettesByMount [ mount ] as StatePipette ; const probed = probedByMount [ mount ] || false ; const tipOn = tipOnByMount [ mount ] || false ; const fullPipette = { ... pipette , probed , tipOn , modelSpecs : getPipetteModelSpecs ( pipette ?. name as PipetteModel ) || null , requestedAs : pipette ?. requestedAs || null , } ; return fullPipette ; } ) ; } ) ; export function getApiLevel ( state ) { return session ( state ) . apiLevel ; } export const getNextPipette = createSelector ( getPipettes , ( pipettes ) : Pipette | null => { const usedPipettes = pipettes . filter ( p => p . tipRacks . length > 0 ) ; const nextPipette = usedPipettes . find ( i => ! i . probed ) ; return nextPipette || usedPipettes [ 0 ] || null ; } ) ; export const getCalibrator = createSelector ( getPipettes , pipettes => pipettes . find ( i => i . tipOn ) ?? pipettes [ 0 ] ) ; export function getCalibratorMount ( state ) { const calibrator = getCalibrator ( state ) ; if ( ! calibrator ) return null ; return calibrator . mount ; } export const getPipettesCalibrated = createSelector ( getPipettes , pipettes => pipettes . length !== 0 && pipettes . every ( i => i . probed ) ) ; export function getModulesBySlot ( state ) { return session ( state ) . modulesBySlot ; } export const getModules = createSelector ( getModulesBySlot , modulesBySlot => Object . keys ( modulesBySlot ) . map < SessionModule | undefined > ( ( slot ) => modulesBySlot [ slot as keyof typeof modulesBySlot ] ) . filter < SessionModule > ( ( s ) : s is SessionModule => s !== undefined ) ) ; export const getModulesByProtocolLoadOrder = createSelector ( getModules , modules => modules . slice ( ) . sort ( ( a , b ) => a . protocolLoadOrder - b . protocolLoadOrder ) ) ; export const getModulesByModel = createSelector ( getModules , modules => { return modules . reduce < { [ model in ModuleModel ] ? : SessionModule [ ] ; } > ( ( acc , val ) => { if ( ! acc [ val . model ] ) { acc [ val . model ] = [ ] ; } acc [ val . model ] ?. push ( val ) ; return acc ; } , { } ) ; } ) ; export function getLabwareBySlot ( state ) { return session ( state ) . labwareBySlot ; } export const getLabware = createSelector ( getPipettesByMount , getLabwareBySlot , ( state ) => calibration ( state ) . confirmedBySlot , getModulesBySlot , getCalibrationRequest , getLabwareDefBySlot , getCustomLabwareDefinitions , ( instByMount , lwBySlot , confirmedBySlot , modulesBySlot , calibrationRequest , labwareDefsBySlot , customLabwareDefs ) : Labware [ ] => { return Object . keys ( lwBySlot ) . filter ( isSlot ) . map ( slot => { const labware = lwBySlot [ slot as Slot ] as Labware ; const { type , isTiprack , isLegacy } = labware ?? { } ; let definition = null ; if ( ! isLegacy ) { definition = labwareDefsBySlot [ slot ] || getLatestLabwareDef ( type ) || customLabwareDefs . find ( d => d . parameters . loadName === type ) || null ; } const confirmed = some ( confirmedBySlot , ( value , key ) => value === true && ( key === slot || ( ! isTiprack && type === lwBySlot [ key as Slot ] ?. type && modulesBySlot [ key as Slot ] ?. model === modulesBySlot [ slot as Slot ] ?. model ) ) ) ; let calibration = 'unconfirmed' ; let isMoving = false ; if ( calibrationRequest . slot === slot && ! calibrationRequest . error ) { const { type , inProgress } = calibrationRequest ; isMoving = inProgress && type !== 'JOG' ; if ( type === 'MOVE_TO' ) { calibration = inProgress ? 'moving-to-slot' : 'over-slot' ; } else if ( type === 'JOG' ) { calibration = inProgress ? 'jogging' : 'over-slot' ; } else if ( type === 'DROP_TIP_AND_HOME' ) { calibration = inProgress ? 'dropping-tip' : 'over-slot' ; } else if ( type === 'PICKUP_AND_HOME' ) { calibration = inProgress ? 'picking-up' : 'picked-up' ; } else if ( type === 'CONFIRM_TIPRACK' || type === 'UPDATE_OFFSET' ) { calibration = inProgress ? 'confirming' : 'confirmed' ; } } const fullLabware = { ... labware , calibration , confirmed , isMoving , definition , } ; return fullLabware ; } ) . sort ( ( a , b ) => { if ( a . isTiprack && ! b . isTiprack ) return - 1 ; if ( ! a . isTiprack && b . isTiprack ) return 1 ; if ( ! a . isTiprack && ! b . isTiprack ) return 0 ; const aChannels = a . calibratorMount != null ? instByMount [ a . calibratorMount as Mount ] ?. channels ?? 0 : 0 ; const bChannels = b . calibratorMount != null ? instByMount [ b . calibratorMount as Mount ] ?. channels ?? 0 : 0 ; return bChannels - aChannels ; } ) ; } ) ; export function getModulesReviewed ( state ) { return Boolean ( calibration ( state ) . modulesReviewed ) ; } export function getDeckPopulated ( state ) { const { deckPopulated } = calibration ( state ) ; return deckPopulated != null ? deckPopulated : null ; } export const getUnconfirmedLabware = createSelector ( getLabware , labware => labware . filter ( lw => lw . type && ! lw . confirmed ) ) ; export const getTipracks = createSelector ( getLabware , labware => labware . filter ( lw => lw . type && lw . isTiprack ) ) ; export const getNotTipracks = createSelector ( getLabware , labware => labware . filter ( lw => lw . type && ! lw . isTiprack ) ) ; export const getUnconfirmedTipracks = createSelector ( getUnconfirmedLabware , labware => labware . filter ( lw => lw . type && lw . isTiprack ) ) ; export const getNextLabware = createSelector ( getUnconfirmedTipracks , getUnconfirmedLabware , ( tipracks , labware ) => tipracks [ 0 ] ?? labware [ 0 ] ) ; export const getTipracksConfirmed = createSelector ( getUnconfirmedTipracks , remaining => remaining . length === 0 ) ; export const getLabwareConfirmed = createSelector ( getUnconfirmedLabware , remaining => remaining . length === 0 ) ; export function getJogInProgress ( state ) { const request = getCalibrationRequest ( state ) ; return request . type === 'JOG' && request . inProgress ; } export function getOffsetUpdateInProgress ( state ) { const request = getCalibrationRequest ( state ) ; return request . type === 'UPDATE_OFFSET' && request . inProgress ; } export function getReturnTipInProgress ( state ) { const request = getCalibrationRequest ( state ) ; return request . type === 'RETURN_TIP' && request . inProgress ; } export const getTipracksByMount = createSelector ( getTipracks , getPipettesByMount , ( tipracks , pipettesMap ) => { return Constants . PIPETTE_MOUNTS . reduce < TipracksByMountMap > ( ( tiprackMap , mount ) => { const byCalibrator = tipracks . filter ( tr => tr . calibratorMount === mount ) ; const byTiprackList = tipracks . filter ( tr => ( pipettesMap [ mount ] ?. tipRacks ?? [ ] ) . includes ( tr . _id ) ) ; tiprackMap [ mount ] = uniqBy ( byCalibrator . concat ( byTiprackList ) , item => item . definitionHash ?? item . _id ) ; return tiprackMap ; } , { left : [ ] , right : [ ] } ) ; } ) ; export const getNextTiprackPipette = uncalibratedTipracksByMount => { const targetMount = head ( Constants . PIPETTE_MOUNTS . filter ( mount => uncalibratedTipracksByMount [ mount ] . length > 0 ) ) ; if ( targetMount ) { return { mount : targetMount , tiprack : uncalibratedTipracksByMount [ targetMount ] [ 0 ] , } ; } else { return null ; } } ;