import isIp from 'is-ip' ; import concat from 'lodash/concat' ; import find from 'lodash/find' ; import head from 'lodash/head' ; import { createSelector } from 'reselect' ; import semver from 'semver' ; import { HEALTH_STATUS_OK , HEALTH_STATUS_UNREACHABLE , RE_HOSTNAME_IPV6_LL , RE_HOSTNAME_IPV4_LL , RE_HOSTNAME_LOCALHOST , RE_HOSTNAME_LOOPBACK , CONNECTABLE , REACHABLE , UNREACHABLE , } from './constants' ; import { getConnectedRobotName } from '../robot/selectors' ; import type { State } from '../types' ; import { DiscoveredRobot , Robot , ReachableRobot , UnreachableRobot , ViewableRobot , } from './types' ; type GetConnectableRobots = ( state : State ) => Robot [ ] ; type GetReachableRobots = ( state : State ) => ReachableRobot [ ] ; type GetUnreachableRobots = ( state : State ) => UnreachableRobot [ ] ; type GetAllRobots = ( state : State ) => DiscoveredRobot [ ] ; type GetViewableRobots = ( state : State ) => ViewableRobot [ ] ; type GetConnectedRobot = ( state : State ) => Robot | null ; const makeDisplayName = ( name ) : string => name . replace ( 'opentrons-' , '' ) ; const isLocal = ( ip ) : boolean => { return ( RE_HOSTNAME_IPV6_LL . test ( ip ) || RE_HOSTNAME_IPV4_LL . test ( ip ) || RE_HOSTNAME_LOCALHOST . test ( ip ) || RE_HOSTNAME_LOOPBACK . test ( ip ) ) ; } ; const ipToHostname = ( ip ) : string => ( isIp . v6 ( ip ) ? `[${ ip }]` : ip ) ; export function getScanning ( state ) { return state . discovery . scanning ; } export const getDiscoveredRobots = createSelector ( state => state . discovery . robotsByName , getConnectedRobotName , ( robotsMap , connectedRobotName ) => { return Object . keys ( robotsMap ) . map ( ( robotName ) => { const robot = robotsMap [ robotName ] ; const { addresses , ... robotState } = robot ; const { health } = robotState ; const addr = head ( addresses ) ; const ip = addr ?. ip ? ipToHostname ( addr . ip ) : null ; const port = addr ?. port ?? null ; const healthStatus = addr ?. healthStatus ?? null ; const serverHealthStatus = addr ?. serverHealthStatus ?? null ; const baseRobot = { ... robotState , displayName : makeDisplayName ( robotName ) , connected : robotName === connectedRobotName , local : ip !== null ? isLocal ( ip ) : null , seen : addr ?. seen === true , } ; if ( ip !== null && port !== null && healthStatus && serverHealthStatus ) { if ( health && healthStatus === HEALTH_STATUS_OK ) { return { ... baseRobot , ip , port , health , serverHealthStatus , healthStatus : HEALTH_STATUS_OK , status : CONNECTABLE , } ; } if ( healthStatus !== HEALTH_STATUS_UNREACHABLE || addr ?. seen ) { return { ... baseRobot , ip , port , healthStatus , serverHealthStatus , status : REACHABLE , } ; } } return { ... baseRobot , ip , port , healthStatus , serverHealthStatus , status : UNREACHABLE , } ; } ) ; } ) ; export const getConnectableRobots = createSelector ( getDiscoveredRobots , robots => robots . flatMap ( r => ( r . status === CONNECTABLE ? [ r ] : [ ] ) ) ) ; export const getReachableRobots = createSelector ( getDiscoveredRobots , robots => robots . flatMap ( r => ( r . status === REACHABLE ? [ r ] : [ ] ) ) ) ; export const getUnreachableRobots = createSelector ( getDiscoveredRobots , robots => robots . flatMap ( r => ( r . status === UNREACHABLE ? [ r ] : [ ] ) ) ) ; export const getAllRobots = createSelector ( getConnectableRobots , getReachableRobots , getUnreachableRobots , ( cr , rr , ur ) => concat < DiscoveredRobot > ( cr , rr , ur ) ) ; export const getViewableRobots = createSelector ( getConnectableRobots , getReachableRobots , ( cr , rr ) => concat < ViewableRobot > ( cr , rr ) ) ; export const getConnectedRobot = createSelector ( getConnectableRobots , robots => find ( robots , 'connected' ) ?? null ) ; export const getRobotByName = ( state , robotName ) : ViewableRobot | null => { return getViewableRobots ( state ) . find ( r => r . name === robotName ) || null ; } ; export const getRobotApiVersion = ( robot ) : string | null => ( robot . health && semver . valid ( robot . health . api_version ) ) ?? ( robot . serverHealth && semver . valid ( robot . serverHealth . apiServerVersion ) ) ?? null ; export const getRobotFirmwareVersion = ( robot ) : string | null => ( robot . health && robot . health . fw_version ) ?? ( robot . serverHealth && robot . serverHealth . smoothieVersion ) ?? null ; export const getRobotProtocolApiVersion = ( robot ) : { min : string ; max : string ; } | null => { const healthField = robot . health ; const DEFAULT_API_VERSION = '1.0' ; if ( ! healthField ) { return null ; } const maxApiVersion = healthField . protocol_api_version ?? healthField . maximum_protocol_api_version ; const minApiVersion = healthField . minimum_protocol_api_version ; return { min : minApiVersion ? minApiVersion . join ( '.' ) : DEFAULT_API_VERSION , max : maxApiVersion ? maxApiVersion . join ( '.' ) : DEFAULT_API_VERSION , } ; } ; export const getRobotApiVersionByName = ( state , robotName ) : string | null => { const robot = getRobotByName ( state , robotName ) ; return robot ? getRobotApiVersion ( robot ) : null ; } ;