Skip to content

Instantly share code, notes, and snippets.

@mcous
Last active August 11, 2018 04:22
Show Gist options
  • Save mcous/3ba833f8cc7252e7cb5e84cfae4554fe to your computer and use it in GitHub Desktop.
Save mcous/3ba833f8cc7252e7cb5e84cfae4554fe to your computer and use it in GitHub Desktop.
ot2-pause-handler
node_modules

OT2 Pause Handler

Setup

Requirements

  • Node v8 LTS
  • npm v6
    • Once Node is installed, use npm to upgrade itself
    • npm install --global npm@latest
  • Clone or download this repository
    • git clone https://gist.github.com/3ba833f8cc7252e7cb5e84cfae4554fe.git ot2-pause-handler
    • Or Download it as ZIP

Once downloaded

# install dependencies
cd path/to/ot2-pause-handler
npm install

Usage

  1. Open the Opentrons App
  2. Upload your protocol with a robot.pause command
  3. Start the script with:
# by default, use USB-to-ethernet adapter address
node index.js

# specify a different IP (e.g. for WiFi)
node index.js "192.168.1.100"
  1. Click "Run" in the app
  2. Script will detect when protocol goes from running to paused and run its doYourThing function
// checking OT2 RPC websocket for session state changes
'use strict'
const execa = require('execa')
const WebSocket = require('ws')
const {get, uniqueId} = require('lodash')
// get host and port from command line, defaulting to USB-to-ethernet address
const argv = process.argv.slice(2)
const HOST = argv[0] || '[fd00:0:cafe:fefe::1]'
const PORT = argv[1] || 31950
// RPC message types
const TYPE_NOTIFICATION = 2
const TYPE_CONTROL = 3
// RPC notification topics
const TOPIC_SESSION = 'session'
// session states
const STATE_RUNNING = 'running'
const STATE_PAUSED = 'paused'
let ws
let sessionId = null
let previousState = null
// start
initializeRpcClient()
// YOUR CODE HERE
function doYourThing() {
console.log('Doing your thing')
// example shell command to run
execa('sleep', ['1'])
// once the shell command has finished executing, call `resume`
.then(() => call('resume'))
.catch(console.error)
}
function initializeRpcClient () {
const url = `ws://${HOST}:${PORT}`
console.log('Connecting to', url)
ws = new WebSocket(url)
// listen for RPC JSON messages on the websocket
ws.on('message', handleMessage)
}
function call (name, args = []) {
if (!sessionId) {
return console.error("Can't resume because we don't have a session")
}
console.log(`Calling method "${name}" on session ${sessionId}`)
// send a remote call of `resume` on the session object
ws.send(JSON.stringify({
$: {token: uniqueId('token')},
id: sessionId,
name,
args
}))
}
function handleMessage (data) {
const message = parseMessage(data)
// check the message metadata for the type of message
if (message.$.type === TYPE_NOTIFICATION) {
// if it's a notification, pull out the session state and handle do your
// thing if the state changed from running to paused
const {topic, payload: {state}} = getNotificationProps(message)
if (topic === TOPIC_SESSION) {
if (previousState === STATE_RUNNING && state === STATE_PAUSED) {
console.log(`Session changed from running to paused`)
doYourThing()
}
previousState = state
}
} else if (message.$.type === TYPE_CONTROL) {
// if it's the control message (initial message sent by server), grab
// the session ID from the message to make RPC calls later on
sessionId = get(message, 'root.v.session_manager.v.session.i')
if (!sessionId) {
throw new Error('Protocol must be loaded before starting this utility')
}
console.log('Listening to session', sessionId)
}
}
function parseMessage (data) {
try {
return JSON.parse(data)
} catch (e) {
console.warn('Parse error', e)
return {}
}
}
function getNotificationProps(message) {
let {topic, payload} = get(message, 'data.v', {topic: null, payload: null})
return {topic, payload: get(payload, 'v')}
}
{
"name": "ot-pause-notification-client",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
}
},
"execa": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz",
"integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==",
"requires": {
"cross-spawn": "^6.0.0",
"get-stream": "^3.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-eof": "^1.0.0"
}
},
"get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"lodash": {
"version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
},
"nice-try": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz",
"integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA=="
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
"requires": {
"path-key": "^2.0.0"
}
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
"requires": {
"shebang-regex": "^1.0.0"
}
},
"shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"requires": {
"isexe": "^2.0.0"
}
},
"ws": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz",
"integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
}
{
"name": "ot2-pause-handler",
"private": true,
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Mike Cousins <mike@opentrons.com>",
"engines": {
"node": ">=8",
"npm": ">=6"
},
"license": "Apache-2.0",
"dependencies": {
"execa": "^0.10.0",
"lodash": "^4.17.10",
"ws": "^6.0.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment