Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
tjhorner committed Jun 17, 2017
0 parents commit 122e9d5
Show file tree
Hide file tree
Showing 11 changed files with 676 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
test/testconfig.json
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
test/testconfig.json
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# MakerBot JSON-RPC Library for Node.js

(Before you go any further, note that **THIS LIBRARY IS SUPER INCOMPLETE** and will be updated fairly often. So before you complain about something that's missing, **DON'T.**)

This library acts as an abstraction layer for the JSON-RPC methods that MakerBot 3D printers use. At the moment, it supports doing the following:

- **Authentication.** You can authenticate with a Thingiverse OAuth token or locally by pressing on the knob.
- **Get realtime printer status.** You can get the real-time status of the printer (what it's doing, info about the extruder, and much more).
- **Load/unload filament.** You can instruct your printer to start the filament loading/unloading process.
- **Cancel current process.** You can instruct your printer to cancel the current process (unloading/loading filament, printing, assisted calibration, etc.)
- **Print a file.** You can instruct your printer to print a `.makerbot` file remotely.

## Example

```javascript
const MakerbotRpc = require('makerbot-rpc')

var printer = new MakerbotRpc("192.168.1.100", {
authMethod: "thingiverse",
thingiverseToken: "asdasd123123",
username: "tjhorner"
})

// Fired when the initial handshake with the printer is made
printer.on("connected", info => {
console.log("We are connected to the printer!", info)
})

// Fired when authentication is successful, and you can now
// make privileged method calls
printer.on("authenticated", () => {
console.log("We're now authenticated. Hooray!")
// Print the file hello.makerbot
printer.printFile(__dirname + "/hello.makerbot")
.then(printInfo => {
console.log("We have started printing! Here is some info about the print:", printInfo)
})
})

// Fired when the printer sends a `system_notification` around
// every one second. It includes lots of useful stuff, and is
// stored in `MakerbotRpc.state` if you ever need it later
printer.on("state", newState => {
console.log("The printer sent us a new state!", newState)
})
```
224 changes: 224 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
const EventEmitter = require('events')
const request = require('request')
const JsonRpc = require('./lib/jsonrpc')
const fs = require('fs')
const crc = require('crc')

// MakerBot Reflector address, for remote access
const REFLECTOR_BASE = "https://reflector.makerbot.com"

// Client ID/secret, for LAN access
const AUTH_CLIENT_ID = "MakerWare"
const AUTH_CLIENT_SECRET = "secret"

/**
* A client to interact with a MakerBot printer via JSON-RPC.
*
* @class MakerbotRpcClient
* @extends {EventEmitter}
*/
class MakerbotRpcClient extends EventEmitter {
constructor(printerIp, options = { }) {
super()

this.printerIp = printerIp
this.connected = false

this.client = new JsonRpc(printerIp, 9999)

this.client.on("response", res => {
if(res.method === "system_notification") {
this.state = res.params.info
this.emit("state", res)
}
})

// this.client.request("handshake", { })
// .then(res => {
// console.log("HANDSHAKE", res)
// })

this.client.request("handshake", { })
.then(res => {
this.emit("connected", res.result)
this.printerInfo = res.result
this.connected = true

switch(options.authMethod){
case "thingiverse":
this._authenticateThingiverse(options.thingiverseToken, options.username)
break
case "access_token":
this._authenticateAccessToken(options.accessToken)
break
case "local_authorization":
default:
this._authenticateLocal()
break
}
})
}

/**
* Authenticate locally by pushing the knob on the printer.
*
* @memberOf MakerbotRpcClient
*/
_authenticateLocal() {
request(`http://${this.printerIp}/auth`, {
qs: {
response_type: "code",
client_id: AUTH_CLIENT_ID,
client_secret: AUTH_CLIENT_SECRET
},
json: true
}, (err, res, codeBody) => {
// poll every 5s until request is either accepted or rejected
this.emit("auth-push-knob")

var poll = setInterval(function() {
request(`http://${this.printerIp}/auth`, {
qs: {
response_type: "answer",
client_id: AUTH_CLIENT_ID,
client_secret: AUTH_CLIENT_SECRET,
answer_code: body.answer_code
},
json: true
}, (err, res, answerBody) => {
if(answerBody.answer === "accepted") {
// we don't need to poll anymore
clearInterval(poll)

request(`http://${this.printerIp}/auth`, {
qs: {
response_type: "token",
client_id: AUTH_CLIENT_ID,
client_secret: AUTH_CLIENT_SECRET,
context: "jsonrpc",
auth_code: answerBody.code
},
json: true
}, (err, res, tokenBody) => {
// complete the JSON-RPC authentication
_authenticateAccessToken(tokenBody.access_token)
})
}
})
}, 5000)
})
}

_authenticateThingiverse(thingiverseToken, username) {
request(`http://${this.printerIp}/auth`, {
qs: {
response_type: "code",
client_id: AUTH_CLIENT_ID,
client_secret: AUTH_CLIENT_SECRET,
thingiverse_token: thingiverseToken,
username: username
},
json: true
}, (err, res, codeBody) => {
request(`http://${this.printerIp}/auth`, {
qs: {
response_type: "answer",
client_id: AUTH_CLIENT_ID,
client_secret: AUTH_CLIENT_SECRET,
answer_code: codeBody.answer_code
},
json: true
}, (err, res, answerBody) => {
if(answerBody.answer === "accepted") {
request(`http://${this.printerIp}/auth`, {
qs: {
response_type: "token",
client_id: AUTH_CLIENT_ID,
client_secret: AUTH_CLIENT_SECRET,
context: "jsonrpc",
auth_code: answerBody.code
},
json: true
}, (err, res, tokenBody) => {
// complete the JSON-RPC authentication
this._authenticateAccessToken(tokenBody.access_token)
})
}
})
})
}

/**
* Authenticate to JSON-RPC with an `access_token`.
*
* @param {string} accessToken The `access_token` obtained via the printer's HTTP API.
*
* @memberOf MakerbotRpcClient
*/
_authenticateAccessToken(accessToken) {
this.client.request("authenticate", { access_token: accessToken })
.then(res => {
this.emit("authenticated", res)
})
}

getMachineConfig() {
return this.client.request("get_machine_config", { })
}

loadFilament(tool_index = 0) {
return this.client.request("load_filament", { tool_index })
}

unloadFilament(tool_index = 0) {
return this.client.request("unload_filament", { tool_index })
}

cancel() {
return this.client.request("cancel", { })
}

printFile(file) {
// TODO handle errors in this promise
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
this.client.request("print", {
filepath: "print.makerbot",
transfer_wait: true
})
.then(res => {
// resolve the promise with the new print data
resolve(res)

// emulate pressing the "start print" button
this.client.request("process_method", {
method: "build_plate_cleared"
})
return this.client.request("put_init", {
block_size: data.length,
file_id: "1",
file_path: "/current_thing/print.makerbot",
length: data.length
})
})
.then(res => {
return this.client.request("put_raw", {
file_id: "1",
length: data.length
})
})
.then(res => {
this.client.conn.write(data, () => {
this.client.request("put_term", {
crc: crc.crc32(data),
file_id: "1",
length: data.length
})
})
})
})
})
}
}

module.exports = MakerbotRpcClient
64 changes: 64 additions & 0 deletions lib/jsonrpc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const EventEmitter = require('events')
const net = require('net')

var generateId = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8)
return v.toString(16)
})
}

class JsonRpc extends EventEmitter {
constructor(host, port) {
super()

this.isConnected = false
this.conn = net.connect({ host, port })

this.requests = { }
this.currentPacket = ""

this.conn.on("data", data => {
// this is a json packet
if(this.currentPacket.indexOf("{") === 0 || this.currentPacket === "" && data.toString().indexOf("{") === 0){
this.currentPacket += data
var isFullPacket = true

try{
JSON.parse(this.currentPacket)
}catch(e){
isFullPacket = false
}

if(isFullPacket){
var json = JSON.parse(this.currentPacket)
this.currentPacket = ""

this.emit("response", json)

if(this.requests[json.id]){
this.requests[json.id](json)
this.requests[json.id] = null
}
}
}
})
}

request(method, params) {
return new Promise((resolve, reject) => {
var id = generateId()
this.requests[id] = resolve

var req = {
id,
jsonrpc: "2.0",
method, params
}

this.conn.write(JSON.stringify(req))
})
}
}

module.exports = JsonRpc
Loading

0 comments on commit 122e9d5

Please sign in to comment.