Created
June 19, 2025 13:04
-
-
Save isti115/549682aaccb35a60a8f17355a50bd754 to your computer and use it in GitHub Desktop.
Simple controller script to execute some basic commands on an Anycubic ACE Pro.
This file contains hidden or 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
#!/usr/bin/env -S deno run --allow-all | |
/* | |
* Implemented based on: | |
* - https://github.com/utkabobr/DuckACE | |
* - https://github.com/szkrisz/ACEPROSV08/blob/main/PROTOCOL.md | |
* | |
* Connect via USB to the following pins: | |
* | |
* +----+----+ | |
* +-| | | | |
* | +----+----+ | |
* +-| | | | |
* +----+----+ | |
* | |
* +----+----+ | |
* | D- | | | |
* +-+----+----+ | |
* | | D+ | V- | | |
* +-+----+----+ | |
* | | | | |
* +----+----+ | |
* | |
* (ProTip™: Female jumper wires with the plastic cover removed fit perfectly.) | |
* | |
* Set permissions and settings of the serial port: | |
* ``` | |
* sudo chmod 777 /dev/ttyACM0 | |
* stty -F /dev/ttyACM0 raw -echo | |
* ``` | |
* | |
* Run from shell: `./anycubic-color-engine-pro.mjs dry 40 7000 60` | |
*/ | |
// NOTE: Deno 2.3 is required for `using`. | |
using file = await Deno.open("/dev/ttyACM0", { read: true, write: true }); | |
const calculateCRC = (buffer) => | |
buffer.reduce( | |
(crc, byte) => | |
Array.from({ length: 8 }).reduce( | |
(part) => (part >> 1) ^ (part & 0x1 ? 0x8408 : 0), | |
crc ^ byte, | |
), | |
0xffff, | |
); | |
const makeRequest = (request) => { | |
const payload = new TextEncoder().encode(JSON.stringify(request)); | |
const length = new Uint8Array(2); | |
new DataView(length.buffer).setUint16(0, payload.length, true); | |
const crc = new Uint8Array(2); | |
new DataView(crc.buffer).setUint16(0, calculateCRC(payload), true); | |
return new Uint8Array([0xff, 0xaa, ...length, ...payload, ...crc, 0xfe]); | |
}; | |
const requests = { | |
status: () => ({ method: "get_status" }), | |
// e.g. temp: 40 (°C), fan_speed: 7000 (RPM), duration: 60 (minute) | |
dry: (temp, fan_speed, duration) => ({ | |
method: "drying", | |
params: { temp, fan_speed, duration }, | |
}), | |
// e.g. index: 2 (slot number), length: 300 (mm), speed: 15 (mm/s) | |
// The following response means that there is no filament in the given slot: | |
// `{"id":0,"result":{},"code":0,"msg":"empty"}` | |
feed: (index, length, speed) => ({ | |
method: "feed_filament", | |
params: { index, length, speed }, | |
}), | |
// e.g. index: 2 (slot number), length: 300 (mm), speed: 15 (mm/s) | |
unwind: (index, length, speed) => ({ | |
method: "unwind_filament", | |
params: { index, length, speed }, | |
}), | |
}; | |
const sendRequest = async (request) => { | |
// TODO: Add proper ID handling to match requests and responses! | |
file.write(makeRequest({ id: 0, ...request })); | |
// TODO: Read until expected byte `0xFE` instead of waiting for a fixed time! | |
await new Promise((res) => setTimeout(res, 250)); | |
const buf = new Uint8Array(1024); | |
const numberOfBytesRead = await file.read(buf); | |
return new TextDecoder().decode(buf); | |
}; | |
// Send keepalive / heartbeat periodically | |
// setInterval(() => sendRequest(requests.status()), 2000) | |
const [command, ...parameters] = Deno.args; | |
console.log(await sendRequest(requests[command](...parameters.map(Number)))); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment