code wiki / (root) / nx_moonraker_ready_check.nx

nx_moonraker_ready_check.nx source

↩ module page · 130 lines · 5303 B

1// nx_moonraker_ready_check.nx -- pre-print Klipper state gate. 2// 3// Per NISHI_3D_PRINT_BULLETPROOF_PREFLIGHT_ROADMAP ยง1.4 (printer- 4// side ready check): refuse to send print-start unless Klipper is 5// in "ready" state. Catches the failure mode where operator sends 6// a print to a printer in shutdown/error/startup state and Klipper 7// silently ignores it. 8// 9// Composes: 10// nx_moonraker_io -- GET /printer/info roundtrip 11// nx_moonraker_client -- HTTP status parse 12// substring check -- simple text scan for "state":"ready" 13// (full nx_json composition queued v2) 14// 15// Klipper states (per Klipper API server docs): 16// "ready" -- accepting commands, ready to print 17// "startup" -- initializing, not yet ready 18// "shutdown" -- entered error / safe-shutdown state 19// "error" -- runtime error, operator intervention needed 20// 21// Only "ready" returns SAFE. Everything else returns NOT_READY 22// with the actual state-string available for operator diagnosis. 23// 24// license_tier: ORIGINAL 25 26import "nx_syscalls.nx" 27import "nx_moonraker_client.nx" 28import "nx_moonraker_io.nx" 29 30// ===== sealed-enum verdicts ======================================== 31 32const NX_READY_OK: i64 = 0 33const NX_READY_NOT_READY: i64 = 1 34const NX_READY_BAD_RESPONSE: i64 = 2 35const NX_READY_NETWORK_ERR: i64 = 3 36 37func nx_ready_verdict_name(v: i64) -> *u8 { 38 if v == NX_READY_OK { return "OK" } 39 if v == NX_READY_NOT_READY { return "NOT_READY" } 40 if v == NX_READY_BAD_RESPONSE { return "BAD_RESPONSE" } 41 if v == NX_READY_NETWORK_ERR { return "NETWORK_ERR" } 42 return "UNKNOWN" 43} 44 45// ===== response-body substring scan =============================== 46// 47// Returns 1 if `needle` (NUL-terminated) appears in buf[0..len). 48// Plain byte-compare loop -- no regex / no JSON parser needed. 49 50func nx_ready_contains(buf: *u8, len: i64, needle: *u8) -> i64 { 51 var nlen: i64 = 0 52 while needle[nlen] != 0 { nlen = nlen + 1 } 53 if nlen == 0 { return 1 } 54 if len < nlen { return 0 } 55 var i: i64 = 0 56 while i <= len - nlen { 57 var j: i64 = 0 58 var matched: i64 = 1 59 while j < nlen { 60 if buf[i + j] != needle[j] { matched = 0; j = nlen } 61 j = j + 1 62 } 63 if matched == 1 { return 1 } 64 i = i + 1 65 } 66 return 0 67} 68 69// ===== response-body state classification ========================= 70// 71// Given a Moonraker /printer/info response body, return the 72// appropriate ready-check verdict. Looks for "state":"ready" 73// substring (with optional whitespace tolerated via two patterns). 74// On any non-ready state present, NOT_READY. On no state field at 75// all, BAD_RESPONSE. 76 77func nx_ready_classify_body(body: *u8, body_len: i64) -> i64 { 78 // Tight form: "state":"ready" 79 if nx_ready_contains(body, body_len, "\"state\":\"ready\"") == 1 { 80 return NX_READY_OK 81 } 82 // Loose form: "state": "ready" (with space after colon) 83 if nx_ready_contains(body, body_len, "\"state\": \"ready\"") == 1 { 84 return NX_READY_OK 85 } 86 // Some other state present? Look for the "state" key + any value. 87 if nx_ready_contains(body, body_len, "\"state\"") == 1 { 88 return NX_READY_NOT_READY 89 } 90 return NX_READY_BAD_RESPONSE 91} 92 93// ===== loopback variant (test entry) ============================== 94// 95// Issues GET /printer/info to 127.0.0.1:port via nx_mr_get_loopback, 96// parses status, classifies body. Returns one of NX_READY_*. 97 98func nx_moonraker_ready_check_loopback(port: i64, 99 host: *u8, host_len: i64, 100 api_key: *u8, api_key_len: i64, 101 scratch: *u8, scratch_cap: i64, 102 resp_buf: *u8, resp_cap: i64) -> i64 { 103 let n_resp: i64 = nx_mr_get_loopback(port, host, host_len, 104 "/printer/info", 13, 105 api_key, api_key_len, 106 scratch, scratch_cap, 107 resp_buf, resp_cap) 108 if n_resp <= 0 { return NX_READY_NETWORK_ERR } 109 let status: i64 = nx_mr_parse_status(resp_buf, n_resp) 110 if status != 200 { return NX_READY_BAD_RESPONSE } 111 return nx_ready_classify_body(resp_buf, n_resp) 112} 113 114// ===== operator-facing variant (arbitrary IPv4) =================== 115 116func nx_moonraker_ready_check_ipv4(ipv4: i64, port: i64, 117 host: *u8, host_len: i64, 118 api_key: *u8, api_key_len: i64, 119 scratch: *u8, scratch_cap: i64, 120 resp_buf: *u8, resp_cap: i64) -> i64 { 121 let n_resp: i64 = nx_mr_get_ipv4(ipv4, port, host, host_len, 122 "/printer/info", 13, 123 api_key, api_key_len, 124 scratch, scratch_cap, 125 resp_buf, resp_cap) 126 if n_resp <= 0 { return NX_READY_NETWORK_ERR } 127 let status: i64 = nx_mr_parse_status(resp_buf, n_resp) 128 if status != 200 { return NX_READY_BAD_RESPONSE } 129 return nx_ready_classify_body(resp_buf, n_resp) 130}