Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 115 additions & 6 deletions esp32-firmware/artNC.ino
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <WiFi.h>
#include <ESP32Servo.h> // library: "ESP32Servo"
#include <cstring>
#include <cstdio>

// ----------------------------
// Pins
Expand Down Expand Up @@ -35,7 +37,7 @@ static const int32_t MAX_STEPS_PER_CMD = 250000;
// ----------------------------
// Acceleration (new)
// ----------------------------
static const float START_STEPS_PER_SEC = 120.0f; // start speed
static const float START_STEPS_PER_SEC = 90.0f; // start speed (lower to reduce jerk)
static const float ACCEL_STEPS_PER_SEC2 = 4000.0f; // accel/decel (steps/s^2)

// ----------------------------
Expand Down Expand Up @@ -87,6 +89,72 @@ static bool qPop(Cmd& out) {
return true;
}

static inline void qClear() {
qHead = 0;
qTail = 0;
}

// ----------------------------
// Machine state + status
// ----------------------------
enum class MachineState : uint8_t { READY, BUSY, ERROR };

static MachineState machineState = MachineState::READY;
static char machineDetail[96] = "";

static void sendLineAll(const char* line) {
Serial.print(line);
Serial.print("\n");
if (tcpClient && tcpClient.connected()) {
tcpClient.print(line);
tcpClient.print("\n");
}
}

static void publishState(MachineState newState, const char* detail = nullptr) {
bool sameState = (newState == machineState);
bool sameDetail = false;
if (detail == nullptr || detail[0] == 0) {
sameDetail = (machineDetail[0] == 0);
} else {
sameDetail = (strcmp(machineDetail, detail) == 0);
}

if (sameState && sameDetail) return;

machineState = newState;
if (detail && detail[0]) {
strncpy(machineDetail, detail, sizeof(machineDetail) - 1);
machineDetail[sizeof(machineDetail) - 1] = 0;
} else {
machineDetail[0] = 0;
}

const char* label = (newState == MachineState::READY)
? "READY"
: (newState == MachineState::BUSY ? "BUSY" : "ERROR");

char buf[160];
if (machineDetail[0]) {
snprintf(buf, sizeof(buf), "STATE %s %s", label, machineDetail);
} else {
snprintf(buf, sizeof(buf), "STATE %s", label);
}
sendLineAll(buf);
}

static inline void setReady(const char* detail = nullptr) {
publishState(MachineState::READY, detail);
}

static inline void setBusy(const char* detail) {
publishState(MachineState::BUSY, detail);
}

static inline void setErrorState(const char* detail) {
publishState(MachineState::ERROR, detail);
}

// ----------------------------
// Helpers
// ----------------------------
Expand Down Expand Up @@ -148,6 +216,11 @@ static void replyERR(Src src, const char* msg) {
}
}

static void reportError(Src src, const char* msg) {
setErrorState(msg);
replyERR(src, msg);
}

// Parse: "W L-123 R456 F1200"
static bool parseW(const char* s, int32_t& l, int32_t& r, int32_t& f) {
const char* pL = strchr(s, 'L');
Expand Down Expand Up @@ -269,53 +342,65 @@ static void handleLine(Src src, char* line) {
}

if (n == 0) {
setReady();
replyOK(src);
return;
}

// END
if (strcmp(line, "END") == 0) {
setBusy("END");
setEnable(false);
setReady();
replyOK(src);
return;
}

// H
if (strcmp(line, "H") == 0) {
setBusy("HOME");
posL = 0;
posR = 0;
setReady();
replyOK(src);
return;
}

// E 0 / E 1
if (line[0] == 'E') {
setBusy("ENABLE");
if (strstr(line, "0")) {
setEnable(false);
setReady();
replyOK(src);
return;
}
if (strstr(line, "1")) {
setEnable(true);
setReady();
replyOK(src);
return;
}
replyERR(src, "BAD_E");
reportError(src, "BAD_E");
return;
}

// P U / P D
if (line[0] == 'P') {
setBusy("PEN");
if (strstr(line, "U")) {
penUp();
setReady();
replyOK(src);
return;
}
if (strstr(line, "D")) {
penDown();
setReady();
replyOK(src);
return;
}
setReady();
replyOK(src);
return;
}
Expand All @@ -324,15 +409,17 @@ static void handleLine(Src src, char* line) {
if (line[0] == 'W') {
int32_t l, r, f;
if (!parseW(line, l, r, f)) {
replyERR(src, "BAD_W");
reportError(src, "BAD_W");
return;
}
setBusy("MOVE");
moveWheels(l, r, f);
setReady();
replyOK(src);
return;
}

replyERR(src, "UNKNOWN");
reportError(src, "UNKNOWN");
}

// ----------------------------
Expand All @@ -344,6 +431,13 @@ static int usbLen = 0;
static char netBuf[160];
static int netLen = 0;

static inline void resetInputBuffers() {
usbLen = 0;
usbBuf[0] = 0;
netLen = 0;
netBuf[0] = 0;
}

static void pumpUSB() {
while (Serial.available()) {
char c = (char)Serial.read();
Expand All @@ -366,12 +460,26 @@ static void pumpUSB() {
}

static void pumpNET() {
if (tcpClient && !tcpClient.connected()) {
tcpClient.stop();
resetInputBuffers();
qClear();
setEnable(false);
setReady("TCP_LOST");
}

if (!tcpClient || !tcpClient.connected()) {
WiFiClient nc = server.available();
if (nc) {
if (tcpClient) {
tcpClient.stop();
}
tcpClient = nc;
tcpClient.setNoDelay(true);
netLen = 0;
resetInputBuffers();
qClear();
setEnable(false);
setReady("TCP_CONNECTED");
}
return;
}
Expand Down Expand Up @@ -425,6 +533,7 @@ void setup() {
server.begin();
server.setNoDelay(true);

publishState(MachineState::READY, "BOOT");
Serial.print("OK\n");
}

Expand All @@ -438,4 +547,4 @@ void loop() {
}

delay(1);
}
}
41 changes: 37 additions & 4 deletions print.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def read_acode(path: str) -> List[str]:
def connect_tcp(host: str, port: int, connect_timeout: float) -> socket.socket:
s = socket.create_connection((host, port), timeout=connect_timeout)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
recv_line_poll._buf = bytearray()
return s

def recv_line_poll(sock: socket.socket, slice_timeout: float) -> str | None:
Expand All @@ -54,6 +55,11 @@ def recv_line_poll(sock: socket.socket, slice_timeout: float) -> str | None:
recv_line_poll._buf = buf
return None


def print_status_line(label: str, value: str) -> None:
sys.stdout.write(f"[{label}] {value}\n")
sys.stdout.flush()

def estimate_motion_seconds(
line: str,
feed_to_sps: float,
Expand Down Expand Up @@ -122,12 +128,30 @@ def estimate_motion_seconds(

return (2.0 * t_ramp) + t_cruise + 0.30


def handle_sideband(resp: str) -> bool:
if resp.startswith("STATE"):
print_status_line("STATE", resp[6:].strip())
return True
return False


def drain_sideband(sock: socket.socket, max_wait_s: float, poll_slice_s: float) -> None:
deadline = time.time() + max_wait_s
while time.time() < deadline:
resp = recv_line_poll(sock, poll_slice_s)
if resp is None:
break
if not handle_sideband(resp):
break

def wait_ok(
sock: socket.socket,
line: str,
expected_s: float,
hard_cap_s: float,
poll_slice_s: float,
sideband_cb=None,
) -> None:
# Deadline with safe margin
wait_s = max(4.0, expected_s * 3.0 + 2.0)
Expand All @@ -142,6 +166,9 @@ def wait_ok(
if resp is None:
continue

if sideband_cb and sideband_cb(resp):
continue

if resp == "OK":
return
if resp.startswith("ERR"):
Expand All @@ -167,8 +194,8 @@ def main() -> int:
ap.add_argument("--servo-settle-ms", type=int, default=180)

# accel model (match ESP32 defaults)
ap.add_argument("--start-sps", type=float, default=120.0)
ap.add_argument("--accel-sps2", type=float, default=8000.0)
ap.add_argument("--start-sps", type=float, default=90.0)
ap.add_argument("--accel-sps2", type=float, default=4000.0)

ap.add_argument("--start", type=int, default=1)
ap.add_argument("--delay-ms", type=int, default=0)
Expand All @@ -189,7 +216,8 @@ def main() -> int:
sock = connect_tcp(args.host, args.port, args.connect_timeout)

try:
print(f"Connected to {args.host}:{args.port}")
print_status_line("WIFI", f"Connected to {args.host}:{args.port}")
drain_sideband(sock, max_wait_s=1.0, poll_slice_s=args.poll_slice)
for i in range(idx0, n):
line = lines[i]

Expand All @@ -212,6 +240,7 @@ def main() -> int:
expected_s=expected,
hard_cap_s=args.hard_cap,
poll_slice_s=args.poll_slice,
sideband_cb=handle_sideband,
)

print_line_status(i + 1, n, line, "OK")
Expand All @@ -238,11 +267,15 @@ def main() -> int:
print("DONE")
return 0

except (ConnectionError, TimeoutError, RuntimeError) as exc:
print_status_line("ERROR", str(exc))
return 3

finally:
try:
sock.close()
except Exception:
pass

if __name__ == "__main__":
raise SystemExit(main())
raise SystemExit(main())
Loading