Skip to content

Instantly share code, notes, and snippets.

@isti115
Created June 19, 2025 13:04
Show Gist options
  • Save isti115/549682aaccb35a60a8f17355a50bd754 to your computer and use it in GitHub Desktop.
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.
#!/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