Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app,app-shell): usb request via app-shell #12292

Merged
merged 67 commits into from
May 15, 2023

Conversation

brenthagen
Copy link
Contributor

@brenthagen brenthagen commented Mar 14, 2023

This (large) PR enables http communication between the opentrons desktop app and an OT-3 robot via usb serial port connection. It adds a new package usb-bridge/node-client and uses it as follows:

  1. app detects OT-3 usb device connection
  2. app-shell fetches OT-3 serial port path associated with OT-3
  3. app-shell creates a custom serial port http agent singleton
  4. discovery client polls health for assigned hostname opentrons-usb using serial port http agent
  5. for opentrons-usb hostname, app passes electron two-way IPC invoke requestor function to ApiHostProvider host context
  6. api-client uses provided requestor function for http requests
  7. app-shell handles IPC http requests, makes axios requests using serial port http agent

a handful of work left for this or follow-on PRs:

  • handle usb device removal correctly, so robot remains in unavailable list (currently it is removed entirely)
  • pass response status, ok via IPC
  • download robot logs (electron-dl) doesn't work with a custom agent - either inactivate for a usb connection or refactor how app-shell downloads
  • unit testing coverage
  • usb-bridge/node-client documentation
  • remove some cruft

sfoster1 and others added 8 commits February 16, 2023 13:24
Adds a silly node client that uses
https://www.npmjs.com/package/agent-base to build an http agent that
redirects everything through a node serialport stream
https://serialport.io/docs/api-serialport including working through
node-fetch (although right now you have to mess with the url).

Many things to continue working on but this works shockingly well
makes a GET /instruments request from the app device details page via serial cable connection.
facilitated via promise-based electron IPC communication and custom usb HTTP agent created in
discovery-client
@codecov
Copy link

codecov bot commented Mar 14, 2023

Codecov Report

Merging #12292 (c3db0de) into edge (fe13f95) will decrease coverage by 0.11%.
The diff coverage is 7.86%.

Impacted file tree graph

@@            Coverage Diff             @@
##             edge   #12292      +/-   ##
==========================================
- Coverage   73.26%   73.15%   -0.11%     
==========================================
  Files        1508     1488      -20     
  Lines       49453    48997     -456     
  Branches     3006     2903     -103     
==========================================
- Hits        36230    35843     -387     
+ Misses      12764    12698      -66     
+ Partials      459      456       -3     
Flag Coverage Δ
shared-data 76.35% <ø> (+0.93%) ⬆️
step-generation 88.20% <ø> (ø)
system-server 96.07% <ø> (ø)
update-server 65.65% <ø> (ø)
usb-bridge 81.35% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
app-shell/src/buildroot/update.ts 0.00% <0.00%> (ø)
app-shell/src/http.ts 25.71% <0.00%> (ø)
app-shell/src/main.ts 0.00% <ø> (ø)
...alibration/useAllPipetteOffsetCalibrationsQuery.ts 0.00% <0.00%> (ø)
...rc/calibration/useAllTipLengthCalibrationsQuery.ts 0.00% <0.00%> (ø)
...lient/src/calibration/useCalibrationStatusQuery.ts 0.00% <0.00%> (ø)
...aintenance_runs/useCreateMaintenanceRunMutation.ts 77.77% <0.00%> (-11.12%) ⬇️
react-api-client/src/networking/useWifiQuery.ts 0.00% <0.00%> (ø)
...-client/src/protocols/useCreateProtocolMutation.ts 76.92% <0.00%> (-7.70%) ⬇️
react-api-client/src/robot/useSetLightsMutation.ts 0.00% <0.00%> (ø)
... and 6 more

... and 21 files with indirect coverage changes

Copy link
Member

@sfoster1 sfoster1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh man this is so cool

Copy link
Member

@shlokamin shlokamin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fkn awesome! we should do a group review with @sfoster1 to discuss next steps. seems reasonable to merge into the prototyping branch though

@@ -59,6 +59,7 @@
"node-stream-zip": "1.8.2",
"pump": "3.0.0",
"semver": "5.5.0",
"serialport": "^10.5.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we prolly wanna pin this to a fixed version

.getSerialPorts?.()
.find(port => port.productId === DEFAULT_PRODUCT_ID)

// TODO: handle null case properly
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we can't find a serial port we prolly wanna just bail and do nothing for now

Comment on lines 23 to 25
"devDependencies": {
"@opentrons/usb-bridge/node-client": "link:../usb-bridge/node-client"
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a normal dep right?

Comment on lines 182 to 183
* TODO: poll via serial port if connection present and combine in same object if same robot?
* will poll serial port for every robot
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that makes sense to me cc @sfoster1

@@ -54,6 +55,11 @@ export const getRobots: (
}
)

export const getSerialPorts: (state: State) => PortInfo[] = createSelector(
state => state.serialPorts,
serialPorts => serialPorts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can either get rid of line 60 or not use reselect since this is just pulling from state

Comment on lines 278 to 281
robotsByName: robotsByNameReducer,
hostsByIp: hostsByIpReducer,
manualAddresses: manualAddressesReducer,
serialPorts: serialPortsReducer,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we somehow roll serial ports into hostsByIp (we'd have to rename it to be something else), all of the info should be the same it would just be a serial port instad of an ip address. hostsByIp returns a HostsByIpMap interface:

export interface HostsByIpMap {
  [ipAddress: string]: HostState
}

export interface HostState extends Address {
  /** Whether this IP has been seen via mDNS or HTTP while the client has been running */
  seen: boolean
  /** How the last GET /health responded (null if no response yet) */
  healthStatus: HealthStatus | null
  /** How the last GET /server/update/health responded (null if no response yet) */
  serverHealthStatus: HealthStatus | null
  /** Error status and response from /health if last request was not 200 */
  healthError: HealthErrorResponse | null
  /** Error status and response from /server/update/health if last request was not 200 */
  serverHealthError: HealthErrorResponse | null
  /** Robot that this IP points to */
  robotName: string
  /** the robot model advertised in mdns, if known */
  advertisedModel: string | null
}

@brenthagen brenthagen marked this pull request as ready for review May 12, 2023 19:05
@brenthagen brenthagen requested review from a team as code owners May 12, 2023 19:05
@brenthagen brenthagen requested review from b-cooper and removed request for a team May 12, 2023 19:05
@vegano1
Copy link
Contributor

vegano1 commented May 12, 2023

This is so cool!

Copy link
Member

@shlokamin shlokamin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is awesome! tested it out and seems to work pretty smoothly. excited to get this into the next internal release. great job @brenthagen!

Comment on lines +106 to +107
// prefer ip hostname
const isIpSort = isIp(b.ip) ? 1 : -1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -33,6 +34,7 @@
"@babel/eslint-parser": "^7.12.1",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-transform-modules-commonjs": "^7.12.1",
"@babel/plugin-transform-typescript": "^7.14.5",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can already use the types syntax — how come we need this plugin?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this error happens when trying to use the declare keyword:

[shell]     Module build failed (from ../node_modules/babel-loader/lib/index.js):
[shell]     SyntaxError: /Users/brenthagen/opentrons/usb-bridge/node-client/src/usb-agent.ts: TypeScript 'declare' fields must first be transformed by @babel/plugin-transform-typescript.
[shell]     If you have already enabled that plugin (or '@babel/preset-typescript'), make sure that it runs before any plugin related to additional class features:
[shell]      - @babel/plugin-proposal-class-properties
[shell]      - @babel/plugin-proposal-private-methods
[shell]      - @babel/plugin-proposal-decorators
[shell]       140 |   } = { path: '' }
[shell]       141 |
[shell]     > 142 |   declare totalSocketCount: number
[shell]           |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

so i inserted @babel/plugin-transform-typescript above @babel/plugin-proposal-class-properties in our babel config

Comment on lines +5 to +11
## overview

## api

```

```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm we should either fill these sections out or delete em

Comment on lines +1 to +3
#!/usr/bin/env node
'use strict'
require('../lib/cli')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not following why this is necessary. if our ts config is set up correctly (which i think it is?) we shouldn't have to do this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is boilerplate i believe seth copied over from the discovery-client to enable cli requests. I left that in, do we want to take it out?

"dependencies": {
"serialport": "10.5.0",
"yargs": "15.4.0",
"node-fetch": "^2.6.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably pin this to a specific version to avoid surprises

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems reasonable to pin to 2.6.7 in app-shell, discovery-client, and here

Copy link
Member

@shlokamin shlokamin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is awesome! tested it out and seems to work pretty smoothly. excited to get this into the next internal release. great job @brenthagen!

@shlokamin
Copy link
Member

no idea how the last review got duplicated... and i cant seem to delete them

@brenthagen brenthagen merged commit f32f618 into edge May 15, 2023
@brenthagen brenthagen deleted the app_app-shell-usb-request-POC branch May 15, 2023 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants