-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 122e9d5
Showing
11 changed files
with
676 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
test/testconfig.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
test/testconfig.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.