init
This commit is contained in:
0
main/control_plane/src/.gitkeep
Normal file
0
main/control_plane/src/.gitkeep
Normal file
27
main/control_plane/src/controller_lifecycle.c
Normal file
27
main/control_plane/src/controller_lifecycle.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "controller_lifecycle.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "printer_protocol.h"
|
||||
#include "rest_server.h"
|
||||
#include "system_runtime.h"
|
||||
|
||||
static const char *TAG = "controller_lifecycle";
|
||||
|
||||
esp_err_t controller_lifecycle_start(void) {
|
||||
ESP_LOGI(TAG, "Starting system runtime");
|
||||
ESP_ERROR_CHECK(system_runtime_bootstrap());
|
||||
|
||||
ESP_LOGI(TAG, "Starting printer protocol");
|
||||
ESP_ERROR_CHECK(printer_protocol_init());
|
||||
|
||||
ESP_LOGI(TAG, "Starting REST server");
|
||||
ESP_ERROR_CHECK(rest_server_start());
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void controller_lifecycle_stop(void) {
|
||||
rest_server_stop();
|
||||
printer_protocol_disconnect();
|
||||
ESP_LOGI(TAG, "Controller lifecycle stopped");
|
||||
}
|
||||
487
main/control_plane/src/rest_server.c
Normal file
487
main/control_plane/src/rest_server.c
Normal file
@@ -0,0 +1,487 @@
|
||||
#include "rest_server.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "rest_server_internal.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "rest_server";
|
||||
|
||||
static httpd_handle_t s_server;
|
||||
|
||||
static const char *s_web_index =
|
||||
"<!doctype html>\n"
|
||||
"<html lang='en'>\n"
|
||||
"<head>\n"
|
||||
" <meta charset='utf-8'>\n"
|
||||
" <meta name='viewport' content='width=device-width,initial-scale=1'>\n"
|
||||
" <title>LYF Printer Controller</title>\n"
|
||||
" <style>\n"
|
||||
" :root { color-scheme: light; }\n"
|
||||
" body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 16px; background:#f7f8fb; color:#1b2430; }\n"
|
||||
" .box { max-width: 760px; margin: 0 auto; background:#fff; border:1px solid #dfe6ee; border-radius:12px; padding:16px; }\n"
|
||||
" h1 { margin: 0 0 8px 0; font-size: 22px; }\n"
|
||||
" p { margin: 6px 0 12px 0; color:#4b5d73; }\n"
|
||||
" .row { display:flex; gap:8px; flex-wrap:wrap; margin: 8px 0; }\n"
|
||||
" input, textarea, select, button { font: inherit; }\n"
|
||||
" input, textarea, select { width:100%; box-sizing:border-box; border:1px solid #c8d4e3; border-radius:8px; padding:8px 10px; background:#fff; }\n"
|
||||
" textarea { min-height: 100px; resize: vertical; }\n"
|
||||
" button { border:0; border-radius:8px; padding:8px 12px; cursor:pointer; background:#0b66d0; color:#fff; }\n"
|
||||
" button.alt { background:#5f6f82; }\n"
|
||||
" button.warn { background:#cf2f2f; }\n"
|
||||
" pre { background:#0f1720; color:#d6e2f5; border-radius:8px; padding:10px; overflow:auto; min-height:120px; }\n"
|
||||
" .small { font-size: 12px; color:#6a778a; }\n"
|
||||
" label { font-size:13px; color:#44566c; display:block; margin: 10px 0 4px 0; }\n"
|
||||
" </style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
" <div class='box'>\n"
|
||||
" <h1>LYF Printer Controller</h1>\n"
|
||||
" <p>ESP32-S3 local control page. This page calls the onboard REST API directly.</p>\n"
|
||||
"\n"
|
||||
" <label>API Key (optional)</label>\n"
|
||||
" <input id='apiKey' type='text' placeholder='Leave empty if API key is disabled'>\n"
|
||||
"\n"
|
||||
" <div class='row'>\n"
|
||||
" <button id='btnHealth' class='alt'>Health</button>\n"
|
||||
" <button id='btnStatus' class='alt'>Status</button>\n"
|
||||
" <button id='btnJobs' class='alt'>Jobs</button>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <label>Printer Name</label>\n"
|
||||
" <input id='printerName' type='text' value='lyfPrinter'>\n"
|
||||
" <div class='row'>\n"
|
||||
" <button id='btnConnect'>Connect Printer</button>\n"
|
||||
" <button id='btnAutoConnect' class='alt'>Auto Connect</button>\n"
|
||||
" <button id='btnDisconnect' class='warn'>Disconnect</button>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <label>Text to Print</label>\n"
|
||||
" <textarea id='printText'>Hello from ESP32-S3!\nOrder: A1024\nThank you</textarea>\n"
|
||||
" <div class='row'>\n"
|
||||
" <button id='btnPrintText'>Print Text</button>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <label>Image Upload</label>\n"
|
||||
" <input id='imageFile' type='file' accept='image/*'>\n"
|
||||
" <div class='row'>\n"
|
||||
" <input id='imgThreshold' type='number' min='0' max='255' value='160' placeholder='Threshold 0-255' style='max-width:160px'>\n"
|
||||
" <input id='imgMaxHeight' type='number' min='64' max='3000' value='2200' placeholder='Max height' style='max-width:160px'>\n"
|
||||
" <select id='imgDensity' style='max-width:140px'>\n"
|
||||
" <option value='较淡'>较淡</option>\n"
|
||||
" <option value='中等' selected>中等</option>\n"
|
||||
" <option value='较浓'>较浓</option>\n"
|
||||
" <option value='最深'>最深</option>\n"
|
||||
" </select>\n"
|
||||
" </div>\n"
|
||||
" <div class='row'>\n"
|
||||
" <label style='margin:0;display:flex;align-items:center;gap:6px;'>\n"
|
||||
" <input id='imgScaleToWidth' type='checkbox' checked style='width:auto;'>\n"
|
||||
" Scale to 384 width\n"
|
||||
" </label>\n"
|
||||
" <label style='margin:0;display:flex;align-items:center;gap:6px;'>\n"
|
||||
" <input id='imgInvert' type='checkbox' style='width:auto;'>\n"
|
||||
" Invert\n"
|
||||
" </label>\n"
|
||||
" <button id='btnPrintImage'>Print Image</button>\n"
|
||||
" </div>\n"
|
||||
" <div class='small'>Image bytes are uploaded as gray8; ESP32-S3 handles threshold/scale/raster processing.</div>\n"
|
||||
"\n"
|
||||
" <div class='small'>Tip: connect printer first, then submit print jobs.</div>\n"
|
||||
" <pre id='out'>Ready.</pre>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <script>\n"
|
||||
" const out = document.getElementById('out');\n"
|
||||
" const apiKeyEl = document.getElementById('apiKey');\n"
|
||||
" const printerNameEl = document.getElementById('printerName');\n"
|
||||
" const printTextEl = document.getElementById('printText');\n"
|
||||
" const imageFileEl = document.getElementById('imageFile');\n"
|
||||
" const imgThresholdEl = document.getElementById('imgThreshold');\n"
|
||||
" const imgMaxHeightEl = document.getElementById('imgMaxHeight');\n"
|
||||
" const imgDensityEl = document.getElementById('imgDensity');\n"
|
||||
" const imgScaleToWidthEl = document.getElementById('imgScaleToWidth');\n"
|
||||
" const imgInvertEl = document.getElementById('imgInvert');\n"
|
||||
"\n"
|
||||
" function log(obj) {\n"
|
||||
" if (typeof obj === 'string') {\n"
|
||||
" out.textContent = obj;\n"
|
||||
" } else {\n"
|
||||
" out.textContent = JSON.stringify(obj, null, 2);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function headers(withJson) {\n"
|
||||
" const h = {};\n"
|
||||
" const key = apiKeyEl.value.trim();\n"
|
||||
" if (key) h['X-API-Key'] = key;\n"
|
||||
" if (withJson) h['Content-Type'] = 'application/json';\n"
|
||||
" return h;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function toInt(value, fallback, minV, maxV) {\n"
|
||||
" let n = parseInt(value, 10);\n"
|
||||
" if (Number.isNaN(n)) n = fallback;\n"
|
||||
" if (n < minV) n = minV;\n"
|
||||
" if (n > maxV) n = maxV;\n"
|
||||
" return n;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function uint8ToBase64(data) {\n"
|
||||
" let out = '';\n"
|
||||
" const chunkSize = 0x8000;\n"
|
||||
" for (let i = 0; i < data.length; i += chunkSize) {\n"
|
||||
" const chunk = data.subarray(i, i + chunkSize);\n"
|
||||
" out += String.fromCharCode.apply(null, chunk);\n"
|
||||
" }\n"
|
||||
" return btoa(out);\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function loadImage(file) {\n"
|
||||
" if (window.createImageBitmap) {\n"
|
||||
" return window.createImageBitmap(file);\n"
|
||||
" }\n"
|
||||
" return new Promise((resolve, reject) => {\n"
|
||||
" const url = URL.createObjectURL(file);\n"
|
||||
" const img = new Image();\n"
|
||||
" img.onload = () => {\n"
|
||||
" URL.revokeObjectURL(url);\n"
|
||||
" resolve(img);\n"
|
||||
" };\n"
|
||||
" img.onerror = () => {\n"
|
||||
" URL.revokeObjectURL(url);\n"
|
||||
" reject(new Error('image decode failed'));\n"
|
||||
" };\n"
|
||||
" img.src = url;\n"
|
||||
" });\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" async function buildImagePayload(file) {\n"
|
||||
" if (!file) {\n"
|
||||
" throw new Error('please select an image first');\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" const src = await loadImage(file);\n"
|
||||
" const srcW = src.width || src.naturalWidth || 0;\n"
|
||||
" const srcH = src.height || src.naturalHeight || 0;\n"
|
||||
" if (srcW <= 0 || srcH <= 0) {\n"
|
||||
" throw new Error('invalid image size');\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // Keep request body under 1MB limit in read_body().\n"
|
||||
" const maxRawBytes = 680000;\n"
|
||||
" let outW = srcW;\n"
|
||||
" let outH = srcH;\n"
|
||||
" const pixels = srcW * srcH;\n"
|
||||
" if (pixels > maxRawBytes) {\n"
|
||||
" const scale = Math.sqrt(maxRawBytes / pixels);\n"
|
||||
" outW = Math.max(1, Math.floor(srcW * scale));\n"
|
||||
" outH = Math.max(1, Math.floor(srcH * scale));\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" const canvas = document.createElement('canvas');\n"
|
||||
" canvas.width = outW;\n"
|
||||
" canvas.height = outH;\n"
|
||||
" const ctx = canvas.getContext('2d', { willReadFrequently: true });\n"
|
||||
" ctx.drawImage(src, 0, 0, outW, outH);\n"
|
||||
" if (typeof src.close === 'function') {\n"
|
||||
" src.close();\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" const rgba = ctx.getImageData(0, 0, outW, outH).data;\n"
|
||||
" const gray = new Uint8Array(outW * outH);\n"
|
||||
" for (let i = 0, j = 0; i < rgba.length; i += 4, ++j) {\n"
|
||||
" const r = rgba[i];\n"
|
||||
" const g = rgba[i + 1];\n"
|
||||
" const b = rgba[i + 2];\n"
|
||||
" gray[j] = (77 * r + 150 * g + 29 * b) >> 8;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return {\n"
|
||||
" payload: {\n"
|
||||
" width: outW,\n"
|
||||
" height: outH,\n"
|
||||
" encoding: 'base64_gray8',\n"
|
||||
" data: uint8ToBase64(gray),\n"
|
||||
" scale_to_width: !!imgScaleToWidthEl.checked,\n"
|
||||
" threshold: toInt(imgThresholdEl.value, 160, 0, 255),\n"
|
||||
" invert: !!imgInvertEl.checked,\n"
|
||||
" max_height: toInt(imgMaxHeightEl.value, 2200, 64, 3000),\n"
|
||||
" density: imgDensityEl.value || '中等'\n"
|
||||
" },\n"
|
||||
" srcW,\n"
|
||||
" srcH,\n"
|
||||
" outW,\n"
|
||||
" outH\n"
|
||||
" };\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" async function callApi(path, method, body) {\n"
|
||||
" const init = { method, headers: headers(body !== undefined) };\n"
|
||||
" if (body !== undefined) init.body = JSON.stringify(body);\n"
|
||||
" const r = await fetch(path, init);\n"
|
||||
" const text = await r.text();\n"
|
||||
" let data = text;\n"
|
||||
" try { data = JSON.parse(text); } catch (_) {}\n"
|
||||
" if (!r.ok) {\n"
|
||||
" throw { status: r.status, data };\n"
|
||||
" }\n"
|
||||
" return data;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" async function run(fn) {\n"
|
||||
" try {\n"
|
||||
" const data = await fn();\n"
|
||||
" log(data);\n"
|
||||
" } catch (e) {\n"
|
||||
" log({ ok: false, error: e });\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" document.getElementById('btnHealth').onclick = () => run(() => callApi('/v1/health', 'GET'));\n"
|
||||
" document.getElementById('btnStatus').onclick = () => run(() => callApi('/v1/printer/status', 'GET'));\n"
|
||||
" document.getElementById('btnJobs').onclick = () => run(() => callApi('/v1/jobs', 'GET'));\n"
|
||||
"\n"
|
||||
" document.getElementById('btnConnect').onclick = () => run(() => callApi('/v1/printer/connect', 'POST', {\n"
|
||||
" name: printerNameEl.value.trim() || 'lyfPrinter',\n"
|
||||
" timeout_ms: 15000\n"
|
||||
" }));\n"
|
||||
"\n"
|
||||
" document.getElementById('btnAutoConnect').onclick = () => run(() => callApi('/v1/printer/connect', 'POST', {\n"
|
||||
" name: '*',\n"
|
||||
" timeout_ms: 20000\n"
|
||||
" }));\n"
|
||||
"\n"
|
||||
" document.getElementById('btnDisconnect').onclick = () => run(() => callApi('/v1/printer/disconnect', 'POST', {}));\n"
|
||||
"\n"
|
||||
" document.getElementById('btnPrintText').onclick = () => run(() => callApi('/v1/print/text', 'POST', {\n"
|
||||
" text: printTextEl.value,\n"
|
||||
" density: '中等',\n"
|
||||
" scale: 2,\n"
|
||||
" line_spacing: 2,\n"
|
||||
" max_height: 1800\n"
|
||||
" }));\n"
|
||||
"\n"
|
||||
" document.getElementById('btnPrintImage').onclick = () => run(async () => {\n"
|
||||
" const file = imageFileEl.files && imageFileEl.files[0] ? imageFileEl.files[0] : null;\n"
|
||||
" const converted = await buildImagePayload(file);\n"
|
||||
" const result = await callApi('/v1/print/image', 'POST', converted.payload);\n"
|
||||
" if (converted.srcW !== converted.outW || converted.srcH !== converted.outH) {\n"
|
||||
" result.client_note = 'image resized before upload: ' +\n"
|
||||
" converted.srcW + 'x' + converted.srcH + ' -> ' + converted.outW + 'x' + converted.outH;\n"
|
||||
" }\n"
|
||||
" return result;\n"
|
||||
" });\n"
|
||||
" </script>\n"
|
||||
"</body>\n"
|
||||
"</html>\n";
|
||||
|
||||
static esp_err_t index_get(httpd_req_t *req) {
|
||||
httpd_resp_set_type(req, "text/html; charset=utf-8");
|
||||
httpd_resp_set_hdr(req, "Cache-Control", "no-store");
|
||||
return httpd_resp_send(req, s_web_index, HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
static esp_err_t favicon_get(httpd_req_t *req) {
|
||||
httpd_resp_set_status(req, "204 No Content");
|
||||
return httpd_resp_send(req, NULL, 0);
|
||||
}
|
||||
|
||||
esp_err_t rest_server_start(void) {
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.server_port = CONFIG_LYF_HTTP_PORT;
|
||||
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||
config.max_uri_handlers = 32;
|
||||
|
||||
esp_err_t err = httpd_start(&s_server, &config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_start failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
httpd_uri_t index = {
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = index_get,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &index);
|
||||
|
||||
httpd_uri_t favicon = {
|
||||
.uri = "/favicon.ico",
|
||||
.method = HTTP_GET,
|
||||
.handler = favicon_get,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &favicon);
|
||||
|
||||
httpd_uri_t health = {
|
||||
.uri = "/v1/health",
|
||||
.method = HTTP_GET,
|
||||
.handler = rest_server_health_get,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &health);
|
||||
|
||||
httpd_uri_t connect = {
|
||||
.uri = "/v1/printer/connect",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_connect_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &connect);
|
||||
|
||||
httpd_uri_t disconnect = {
|
||||
.uri = "/v1/printer/disconnect",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_disconnect_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &disconnect);
|
||||
|
||||
httpd_uri_t status = {
|
||||
.uri = "/v1/printer/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = rest_server_status_get,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &status);
|
||||
|
||||
httpd_uri_t print_raster = {
|
||||
.uri = "/v1/print/raster",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_print_raster_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &print_raster);
|
||||
|
||||
httpd_uri_t print_image = {
|
||||
.uri = "/v1/print/image",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_print_image_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &print_image);
|
||||
|
||||
httpd_uri_t print_qr = {
|
||||
.uri = "/v1/print/qr",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_print_qr_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &print_qr);
|
||||
|
||||
httpd_uri_t print_text = {
|
||||
.uri = "/v1/print/text",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_print_text_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &print_text);
|
||||
|
||||
httpd_uri_t print_label = {
|
||||
.uri = "/v1/print/label",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_print_label_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &print_label);
|
||||
|
||||
httpd_uri_t print_receipt = {
|
||||
.uri = "/v1/print/receipt",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_print_receipt_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &print_receipt);
|
||||
|
||||
httpd_uri_t jobs = {
|
||||
.uri = "/v1/jobs",
|
||||
.method = HTTP_GET,
|
||||
.handler = rest_server_jobs_get,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &jobs);
|
||||
|
||||
httpd_uri_t jobs_delete_uri = {
|
||||
.uri = "/v1/jobs",
|
||||
.method = HTTP_DELETE,
|
||||
.handler = rest_server_jobs_delete,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &jobs_delete_uri);
|
||||
|
||||
httpd_uri_t job = {
|
||||
.uri = "/v1/jobs/*",
|
||||
.method = HTTP_GET,
|
||||
.handler = rest_server_job_get,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &job);
|
||||
|
||||
httpd_uri_t job_delete_uri = {
|
||||
.uri = "/v1/jobs/*",
|
||||
.method = HTTP_DELETE,
|
||||
.handler = rest_server_job_delete,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &job_delete_uri);
|
||||
|
||||
httpd_uri_t label_gap = {
|
||||
.uri = "/v1/label/gap_move",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_label_gap_move_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &label_gap);
|
||||
|
||||
httpd_uri_t label_offset_get_uri = {
|
||||
.uri = "/v1/label/offset",
|
||||
.method = HTTP_GET,
|
||||
.handler = rest_server_label_offset_get,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &label_offset_get_uri);
|
||||
|
||||
httpd_uri_t label_offset_post_uri = {
|
||||
.uri = "/v1/label/offset",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_label_offset_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &label_offset_post_uri);
|
||||
|
||||
httpd_uri_t ota_version = {
|
||||
.uri = "/v1/ota/version",
|
||||
.method = HTTP_GET,
|
||||
.handler = rest_server_ota_version_get,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &ota_version);
|
||||
|
||||
httpd_uri_t ota_jump_boot = {
|
||||
.uri = "/v1/ota/jump_boot",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_ota_jump_boot_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &ota_jump_boot);
|
||||
|
||||
httpd_uri_t ota_jump_app = {
|
||||
.uri = "/v1/ota/jump_app",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_ota_jump_app_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &ota_jump_app);
|
||||
|
||||
httpd_uri_t ota_erase_page = {
|
||||
.uri = "/v1/ota/erase_page",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_ota_erase_page_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &ota_erase_page);
|
||||
|
||||
httpd_uri_t ota_write_frame = {
|
||||
.uri = "/v1/ota/write_frame",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_ota_write_frame_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &ota_write_frame);
|
||||
|
||||
httpd_uri_t ota_upgrade = {
|
||||
.uri = "/v1/ota/upgrade",
|
||||
.method = HTTP_POST,
|
||||
.handler = rest_server_ota_upgrade_post,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &ota_upgrade);
|
||||
|
||||
ESP_LOGI(TAG, "REST server started on port %d", CONFIG_LYF_HTTP_PORT);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void rest_server_stop(void) {
|
||||
if (s_server != NULL) {
|
||||
httpd_stop(s_server);
|
||||
s_server = NULL;
|
||||
}
|
||||
}
|
||||
164
main/control_plane/src/rest_server_common.c
Normal file
164
main/control_plane/src/rest_server_common.c
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "rest_server_internal.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mbedtls/base64.h"
|
||||
bool rest_server_auth_ok(httpd_req_t *req) {
|
||||
if (strlen(CONFIG_LYF_API_KEY) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char buf[96] = {0};
|
||||
if (httpd_req_get_hdr_value_str(req, "X-API-Key", buf, sizeof(buf)) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return strcmp(buf, CONFIG_LYF_API_KEY) == 0;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_send_json(httpd_req_t *req, const char *status, cJSON *root) {
|
||||
char *text = cJSON_PrintUnformatted(root);
|
||||
if (text == NULL) {
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "json encode failed");
|
||||
}
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_set_status(req, status);
|
||||
esp_err_t err = httpd_resp_sendstr(req, text);
|
||||
cJSON_free(text);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_send_error(httpd_req_t *req, const char *status, const char *message) {
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", false);
|
||||
cJSON_AddStringToObject(root, "error", message != NULL ? message : "unknown");
|
||||
esp_err_t err = rest_server_send_json(req, status, root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_read_body(httpd_req_t *req, char **out_body) {
|
||||
if (out_body == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
*out_body = NULL;
|
||||
|
||||
if (req->content_len <= 0 || req->content_len > (1024 * 1024)) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
char *buf = (char *)calloc(1, req->content_len + 1);
|
||||
if (buf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
int received = 0;
|
||||
while (received < req->content_len) {
|
||||
int ret = httpd_req_recv(req, buf + received, req->content_len - received);
|
||||
if (ret <= 0) {
|
||||
free(buf);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
received += ret;
|
||||
}
|
||||
|
||||
*out_body = buf;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool rest_server_parse_uri_u32_tail(const char *uri, uint32_t *out_value) {
|
||||
if (uri == NULL || out_value == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *slash = strrchr(uri, '/');
|
||||
if (slash == NULL || *(slash + 1) == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
char *endptr = NULL;
|
||||
unsigned long val = strtoul(slash + 1, &endptr, 10);
|
||||
if (errno != 0 || endptr == slash + 1 || *endptr != '\0' || val > UINT32_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out_value = (uint32_t)val;
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_base64_decode_alloc(const char *b64, uint8_t **out_raw, size_t *out_len) {
|
||||
if (b64 == NULL || out_raw == NULL || out_len == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
*out_raw = NULL;
|
||||
*out_len = 0;
|
||||
|
||||
size_t decoded_len = 0;
|
||||
int rc = mbedtls_base64_decode(NULL,
|
||||
0,
|
||||
&decoded_len,
|
||||
(const unsigned char *)b64,
|
||||
strlen(b64));
|
||||
if (!(rc == 0 || rc == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
uint8_t *buf = (uint8_t *)malloc(decoded_len);
|
||||
if (buf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
rc = mbedtls_base64_decode(buf,
|
||||
decoded_len,
|
||||
&decoded_len,
|
||||
(const unsigned char *)b64,
|
||||
strlen(b64));
|
||||
if (rc != 0) {
|
||||
free(buf);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
*out_raw = buf;
|
||||
*out_len = decoded_len;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void rest_server_appendf(char *dst, size_t cap, size_t *offset, const char *fmt, ...) {
|
||||
if (dst == NULL || cap == 0 || offset == NULL || fmt == NULL || *offset >= cap) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int n = vsnprintf(dst + *offset, cap - *offset, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (n <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t written = (size_t)n;
|
||||
if (written >= cap - *offset) {
|
||||
*offset = cap - 1;
|
||||
return;
|
||||
}
|
||||
*offset += written;
|
||||
}
|
||||
|
||||
bool rest_server_json_bool_with_default(cJSON *item, bool default_value) {
|
||||
if (item == NULL) {
|
||||
return default_value;
|
||||
}
|
||||
if (cJSON_IsBool(item)) {
|
||||
return cJSON_IsTrue(item);
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
173
main/control_plane/src/rest_server_jobs.c
Normal file
173
main/control_plane/src/rest_server_jobs.c
Normal file
@@ -0,0 +1,173 @@
|
||||
#include "rest_server_internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "printer_protocol.h"
|
||||
esp_err_t rest_server_job_get(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
uint32_t job_id = 0;
|
||||
if (!rest_server_parse_uri_u32_tail(req->uri, &job_id)) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid job id");
|
||||
}
|
||||
|
||||
print_job_info_t info;
|
||||
if (!printer_protocol_get_job(job_id, &info)) {
|
||||
return rest_server_send_error(req, "404 Not Found", "job not found");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "job_id", info.id);
|
||||
cJSON_AddStringToObject(root, "state", printer_protocol_job_state_str(info.state));
|
||||
cJSON_AddNumberToObject(root, "progress", info.progress);
|
||||
cJSON_AddStringToObject(root, "density", info.density);
|
||||
cJSON_AddNumberToObject(root, "width", info.width);
|
||||
cJSON_AddNumberToObject(root, "height", info.height);
|
||||
cJSON_AddNumberToObject(root, "data_len", (double)info.data_len);
|
||||
cJSON_AddStringToObject(root, "error", info.error);
|
||||
cJSON_AddNumberToObject(root, "created_ms", (double)info.created_ms);
|
||||
cJSON_AddNumberToObject(root, "started_ms", (double)info.started_ms);
|
||||
cJSON_AddNumberToObject(root, "finished_ms", (double)info.finished_ms);
|
||||
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_jobs_get(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
print_job_info_t jobs[16];
|
||||
size_t total_count = 0;
|
||||
esp_err_t list_rc = printer_protocol_list_jobs(jobs, 16, &total_count);
|
||||
if (list_rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "500 Internal Server Error", "list jobs failed");
|
||||
}
|
||||
|
||||
size_t emit_count = total_count;
|
||||
if (emit_count > 16) {
|
||||
emit_count = 16;
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "total", (double)total_count);
|
||||
cJSON_AddNumberToObject(root, "returned", (double)emit_count);
|
||||
|
||||
cJSON *arr = cJSON_AddArrayToObject(root, "jobs");
|
||||
for (size_t i = 0; i < emit_count; ++i) {
|
||||
cJSON *item = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(item, "job_id", jobs[i].id);
|
||||
cJSON_AddStringToObject(item, "state", printer_protocol_job_state_str(jobs[i].state));
|
||||
cJSON_AddNumberToObject(item, "progress", jobs[i].progress);
|
||||
cJSON_AddStringToObject(item, "density", jobs[i].density);
|
||||
cJSON_AddNumberToObject(item, "width", jobs[i].width);
|
||||
cJSON_AddNumberToObject(item, "height", jobs[i].height);
|
||||
cJSON_AddNumberToObject(item, "data_len", (double)jobs[i].data_len);
|
||||
cJSON_AddStringToObject(item, "error", jobs[i].error);
|
||||
cJSON_AddNumberToObject(item, "created_ms", (double)jobs[i].created_ms);
|
||||
cJSON_AddNumberToObject(item, "started_ms", (double)jobs[i].started_ms);
|
||||
cJSON_AddNumberToObject(item, "finished_ms", (double)jobs[i].finished_ms);
|
||||
cJSON_AddItemToArray(arr, item);
|
||||
}
|
||||
|
||||
if (total_count > emit_count) {
|
||||
cJSON_AddStringToObject(root, "warning", "job list truncated");
|
||||
}
|
||||
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_job_delete(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
uint32_t job_id = 0;
|
||||
if (!rest_server_parse_uri_u32_tail(req->uri, &job_id)) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid job id");
|
||||
}
|
||||
|
||||
char cancel_err[128] = {0};
|
||||
esp_err_t cancel_rc = printer_protocol_cancel_job(job_id, cancel_err, sizeof(cancel_err));
|
||||
if (cancel_rc == ESP_ERR_NOT_FOUND) {
|
||||
return rest_server_send_error(req, "404 Not Found", "job not found");
|
||||
}
|
||||
if (cancel_rc == ESP_ERR_INVALID_STATE) {
|
||||
return rest_server_send_error(req,
|
||||
"409 Conflict",
|
||||
cancel_err[0] != '\0' ? cancel_err : "job already finished");
|
||||
}
|
||||
if (cancel_rc != ESP_OK) {
|
||||
return rest_server_send_error(req,
|
||||
"409 Conflict",
|
||||
cancel_err[0] != '\0' ? cancel_err : "cancel failed");
|
||||
}
|
||||
|
||||
print_job_info_t info;
|
||||
bool ok = printer_protocol_get_job(job_id, &info);
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "job_id", job_id);
|
||||
if (ok) {
|
||||
cJSON_AddStringToObject(root, "state", printer_protocol_job_state_str(info.state));
|
||||
cJSON_AddStringToObject(root, "error", info.error);
|
||||
} else {
|
||||
cJSON_AddStringToObject(root, "state", "canceled");
|
||||
}
|
||||
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_jobs_delete(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
bool include_success = true;
|
||||
bool include_failed = true;
|
||||
bool include_canceled = true;
|
||||
|
||||
if (req->content_len > 0) {
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
include_success = rest_server_json_bool_with_default(cJSON_GetObjectItemCaseSensitive(json, "include_success"), true);
|
||||
include_failed = rest_server_json_bool_with_default(cJSON_GetObjectItemCaseSensitive(json, "include_failed"), true);
|
||||
include_canceled = rest_server_json_bool_with_default(cJSON_GetObjectItemCaseSensitive(json, "include_canceled"), true);
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
|
||||
size_t removed = printer_protocol_cleanup_jobs(include_success, include_failed, include_canceled);
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "removed", (double)removed);
|
||||
cJSON_AddBoolToObject(root, "include_success", include_success);
|
||||
cJSON_AddBoolToObject(root, "include_failed", include_failed);
|
||||
cJSON_AddBoolToObject(root, "include_canceled", include_canceled);
|
||||
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
535
main/control_plane/src/rest_server_ops.c
Normal file
535
main/control_plane/src/rest_server_ops.c
Normal file
@@ -0,0 +1,535 @@
|
||||
#include "rest_server_internal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "printer_protocol.h"
|
||||
#include "system_runtime.h"
|
||||
static void fill_runtime_json(cJSON *root) {
|
||||
char ip[32] = {0};
|
||||
system_runtime_get_ip(ip, sizeof(ip));
|
||||
|
||||
printer_runtime_status_t st = {0};
|
||||
printer_protocol_get_runtime_status(&st);
|
||||
|
||||
cJSON_AddBoolToObject(root, "wifi_ready", system_runtime_wifi_ready());
|
||||
cJSON_AddStringToObject(root, "wifi_mode", "sta");
|
||||
cJSON_AddStringToObject(root, "ip", ip);
|
||||
|
||||
cJSON_AddBoolToObject(root, "ble_connected", st.connected);
|
||||
cJSON_AddBoolToObject(root, "ble_notify_ready", st.notify_ready);
|
||||
cJSON_AddBoolToObject(root, "printer_busy", st.busy);
|
||||
cJSON_AddBoolToObject(root, "has_paper", st.has_paper);
|
||||
cJSON_AddNumberToObject(root, "battery_percent", st.battery_percent);
|
||||
cJSON_AddNumberToObject(root, "temperature", st.temperature);
|
||||
cJSON_AddNumberToObject(root, "queue_depth", st.queue_depth);
|
||||
cJSON_AddNumberToObject(root, "mtu", st.mtu);
|
||||
cJSON_AddNumberToObject(root, "last_status_ms", (double)st.last_status_ms);
|
||||
}
|
||||
|
||||
esp_err_t rest_server_health_get(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
fill_runtime_json(root);
|
||||
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_connect_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
char name[32] = "lyfPrinter";
|
||||
uint32_t timeout_ms = 15000;
|
||||
|
||||
if (req->content_len > 0) {
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
if (json == NULL) {
|
||||
free(body);
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jname = cJSON_GetObjectItemCaseSensitive(json, "name");
|
||||
if (cJSON_IsString(jname) && jname->valuestring != NULL) {
|
||||
strlcpy(name, jname->valuestring, sizeof(name));
|
||||
}
|
||||
|
||||
cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms");
|
||||
if (cJSON_IsNumber(jtimeout) && jtimeout->valuedouble > 0) {
|
||||
timeout_ms = (uint32_t)jtimeout->valuedouble;
|
||||
}
|
||||
|
||||
cJSON_Delete(json);
|
||||
free(body);
|
||||
}
|
||||
|
||||
esp_err_t err = printer_protocol_connect(name, timeout_ms);
|
||||
if (err != ESP_OK) {
|
||||
if (err == ESP_ERR_TIMEOUT) {
|
||||
return rest_server_send_error(req, "504 Gateway Timeout", "connect timeout");
|
||||
}
|
||||
return rest_server_send_error(req, "500 Internal Server Error", "connect failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddStringToObject(root, "message", "connected");
|
||||
fill_runtime_json(root);
|
||||
|
||||
err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_disconnect_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
printer_protocol_disconnect();
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddStringToObject(root, "message", "disconnected");
|
||||
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_status_get(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
fill_runtime_json(root);
|
||||
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_label_gap_move_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
uint32_t timeout_ms = 5000;
|
||||
if (req->content_len > 0) {
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms");
|
||||
if (cJSON_IsNumber(jtimeout) && jtimeout->valuedouble > 0 && jtimeout->valuedouble <= 30000) {
|
||||
timeout_ms = (uint32_t)jtimeout->valuedouble;
|
||||
}
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t rc = printer_protocol_gap_move(timeout_ms, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "gap move failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddStringToObject(root, "message", "gap move success");
|
||||
cJSON_AddNumberToObject(root, "timeout_ms", timeout_ms);
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_label_offset_get(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
uint8_t offset = 0;
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t rc = printer_protocol_get_label_offset(&offset, 3000, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "get offset failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "offset_tenths_mm", offset);
|
||||
cJSON_AddNumberToObject(root, "offset_mm", ((double)offset) / 10.0);
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_label_offset_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jvalue = cJSON_GetObjectItemCaseSensitive(json, "offset_tenths_mm");
|
||||
if (!cJSON_IsNumber(jvalue) || jvalue->valuedouble < 0 || jvalue->valuedouble > 254) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "offset_tenths_mm must be 0..254");
|
||||
}
|
||||
|
||||
uint8_t value = (uint8_t)jvalue->valuedouble;
|
||||
cJSON_Delete(json);
|
||||
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t rc = printer_protocol_set_label_offset(value, 3000, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "set offset failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "offset_tenths_mm", value);
|
||||
cJSON_AddNumberToObject(root, "offset_mm", ((double)value) / 10.0);
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_ota_version_get(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
printer_ota_version_t version = {0};
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t rc = printer_protocol_ota_get_version(&version, 4000, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "get version failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "major", version.major);
|
||||
cJSON_AddNumberToObject(root, "minor", version.minor);
|
||||
cJSON_AddNumberToObject(root, "patch", version.patch);
|
||||
char version_text[32];
|
||||
snprintf(version_text, sizeof(version_text), "V%u.%u.%u", version.major, version.minor, version.patch);
|
||||
cJSON_AddStringToObject(root, "version", version_text);
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_ota_jump_boot_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t rc = printer_protocol_ota_jump_boot(5000, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "jump boot failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddStringToObject(root, "message", "jumped to boot");
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_ota_jump_app_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t rc = printer_protocol_ota_jump_app(5000, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "jump app failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddStringToObject(root, "message", "jumped to app");
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_ota_erase_page_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jpage = cJSON_GetObjectItemCaseSensitive(json, "page_num");
|
||||
cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms");
|
||||
if (!cJSON_IsNumber(jpage) || jpage->valuedouble < 0 || jpage->valuedouble > 65535) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "page_num must be 0..65535");
|
||||
}
|
||||
|
||||
uint32_t timeout_ms = 5000;
|
||||
if (cJSON_IsNumber(jtimeout) && jtimeout->valuedouble > 0 && jtimeout->valuedouble <= 30000) {
|
||||
timeout_ms = (uint32_t)jtimeout->valuedouble;
|
||||
}
|
||||
|
||||
uint16_t page_num = (uint16_t)jpage->valuedouble;
|
||||
cJSON_Delete(json);
|
||||
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t rc = printer_protocol_ota_erase_page(page_num, timeout_ms, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "erase page failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "page_num", page_num);
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_ota_write_frame_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jpack = cJSON_GetObjectItemCaseSensitive(json, "packet_num");
|
||||
cJSON *jlast = cJSON_GetObjectItemCaseSensitive(json, "is_last_frame");
|
||||
cJSON *jdata = cJSON_GetObjectItemCaseSensitive(json, "data");
|
||||
cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms");
|
||||
|
||||
if (!cJSON_IsNumber(jpack) || jpack->valuedouble < 0 || jpack->valuedouble > 65535 ||
|
||||
!cJSON_IsString(jdata) || jdata->valuestring == NULL) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "packet_num and data are required");
|
||||
}
|
||||
|
||||
uint32_t timeout_ms = 4000;
|
||||
if (cJSON_IsNumber(jtimeout) && jtimeout->valuedouble > 0 && jtimeout->valuedouble <= 30000) {
|
||||
timeout_ms = (uint32_t)jtimeout->valuedouble;
|
||||
}
|
||||
bool is_last = rest_server_json_bool_with_default(jlast, false);
|
||||
uint16_t packet_num = (uint16_t)jpack->valuedouble;
|
||||
|
||||
uint8_t *frame_data = NULL;
|
||||
size_t frame_len = 0;
|
||||
esp_err_t b64_rc = rest_server_base64_decode_alloc(jdata->valuestring, &frame_data, &frame_len);
|
||||
cJSON_Delete(json);
|
||||
if (b64_rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid base64 data");
|
||||
}
|
||||
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t rc = printer_protocol_ota_write_frame(packet_num,
|
||||
is_last,
|
||||
frame_data,
|
||||
frame_len,
|
||||
timeout_ms,
|
||||
cmd_err,
|
||||
sizeof(cmd_err));
|
||||
free(frame_data);
|
||||
if (rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "write frame failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "packet_num", packet_num);
|
||||
cJSON_AddBoolToObject(root, "is_last_frame", is_last);
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_ota_upgrade_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jfirmware = cJSON_GetObjectItemCaseSensitive(json, "firmware");
|
||||
cJSON *jjumpboot = cJSON_GetObjectItemCaseSensitive(json, "jump_boot");
|
||||
cJSON *jjumpapp = cJSON_GetObjectItemCaseSensitive(json, "jump_app");
|
||||
cJSON *jpagesize = cJSON_GetObjectItemCaseSensitive(json, "page_size");
|
||||
cJSON *jpacketsize = cJSON_GetObjectItemCaseSensitive(json, "packet_size");
|
||||
cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms_per_step");
|
||||
cJSON *jreadversion = cJSON_GetObjectItemCaseSensitive(json, "read_version_after");
|
||||
|
||||
if (!cJSON_IsString(jfirmware) || jfirmware->valuestring == NULL) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "firmware(base64) is required");
|
||||
}
|
||||
|
||||
bool jump_boot = rest_server_json_bool_with_default(jjumpboot, true);
|
||||
bool jump_app = rest_server_json_bool_with_default(jjumpapp, true);
|
||||
bool read_version_after = rest_server_json_bool_with_default(jreadversion, true);
|
||||
|
||||
uint16_t page_size = 1024;
|
||||
uint16_t packet_size = 236;
|
||||
uint32_t timeout_ms = 5000;
|
||||
if (cJSON_IsNumber(jpagesize) && jpagesize->valuedouble >= 256 && jpagesize->valuedouble <= 4096) {
|
||||
page_size = (uint16_t)jpagesize->valuedouble;
|
||||
}
|
||||
if (cJSON_IsNumber(jpacketsize) && jpacketsize->valuedouble >= 16 && jpacketsize->valuedouble <= 236) {
|
||||
packet_size = (uint16_t)jpacketsize->valuedouble;
|
||||
}
|
||||
if (cJSON_IsNumber(jtimeout) && jtimeout->valuedouble >= 500 && jtimeout->valuedouble <= 60000) {
|
||||
timeout_ms = (uint32_t)jtimeout->valuedouble;
|
||||
}
|
||||
|
||||
uint8_t *firmware = NULL;
|
||||
size_t firmware_len = 0;
|
||||
esp_err_t b64_rc = rest_server_base64_decode_alloc(jfirmware->valuestring, &firmware, &firmware_len);
|
||||
cJSON_Delete(json);
|
||||
if (b64_rc != ESP_OK || firmware_len == 0) {
|
||||
free(firmware);
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid firmware base64");
|
||||
}
|
||||
|
||||
char cmd_err[128] = {0};
|
||||
if (jump_boot) {
|
||||
esp_err_t rc = printer_protocol_ota_jump_boot(timeout_ms, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
free(firmware);
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "jump boot failed");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t total_pages = (uint32_t)((firmware_len + page_size - 1) / page_size);
|
||||
for (uint32_t page = 0; page < total_pages; ++page) {
|
||||
esp_err_t rc = printer_protocol_ota_erase_page((uint16_t)page, timeout_ms, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
free(firmware);
|
||||
return rest_server_send_error(req,
|
||||
"409 Conflict",
|
||||
cmd_err[0] != '\0' ? cmd_err : "erase page failed");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t total_packets = (uint32_t)((firmware_len + packet_size - 1) / packet_size);
|
||||
for (uint32_t packet = 0; packet < total_packets; ++packet) {
|
||||
size_t start = (size_t)packet * packet_size;
|
||||
size_t remain = firmware_len - start;
|
||||
size_t len = remain > packet_size ? packet_size : remain;
|
||||
bool is_last = (packet + 1) == total_packets;
|
||||
|
||||
esp_err_t rc = printer_protocol_ota_write_frame((uint16_t)packet,
|
||||
is_last,
|
||||
firmware + start,
|
||||
len,
|
||||
timeout_ms,
|
||||
cmd_err,
|
||||
sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
free(firmware);
|
||||
return rest_server_send_error(req,
|
||||
"409 Conflict",
|
||||
cmd_err[0] != '\0' ? cmd_err : "write frame failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (jump_app) {
|
||||
esp_err_t rc = printer_protocol_ota_jump_app(timeout_ms, cmd_err, sizeof(cmd_err));
|
||||
if (rc != ESP_OK) {
|
||||
free(firmware);
|
||||
return rest_server_send_error(req, "409 Conflict", cmd_err[0] != '\0' ? cmd_err : "jump app failed");
|
||||
}
|
||||
}
|
||||
|
||||
printer_ota_version_t version = {0};
|
||||
bool version_ok = false;
|
||||
if (read_version_after) {
|
||||
if (printer_protocol_ota_get_version(&version, timeout_ms, cmd_err, sizeof(cmd_err)) == ESP_OK) {
|
||||
version_ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
free(firmware);
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "firmware_len", (double)firmware_len);
|
||||
cJSON_AddNumberToObject(root, "page_size", page_size);
|
||||
cJSON_AddNumberToObject(root, "packet_size", packet_size);
|
||||
cJSON_AddNumberToObject(root, "total_pages", total_pages);
|
||||
cJSON_AddNumberToObject(root, "total_packets", total_packets);
|
||||
cJSON_AddBoolToObject(root, "jump_boot", jump_boot);
|
||||
cJSON_AddBoolToObject(root, "jump_app", jump_app);
|
||||
if (version_ok) {
|
||||
cJSON *v = cJSON_AddObjectToObject(root, "version");
|
||||
cJSON_AddNumberToObject(v, "major", version.major);
|
||||
cJSON_AddNumberToObject(v, "minor", version.minor);
|
||||
cJSON_AddNumberToObject(v, "patch", version.patch);
|
||||
}
|
||||
|
||||
esp_err_t err = rest_server_send_json(req, "200 OK", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
659
main/control_plane/src/rest_server_print.c
Normal file
659
main/control_plane/src/rest_server_print.c
Normal file
@@ -0,0 +1,659 @@
|
||||
#include "rest_server_internal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "printer_protocol.h"
|
||||
#include "raster_tools.h"
|
||||
static esp_err_t submit_raster_job_and_reply(httpd_req_t *req,
|
||||
const uint8_t *raw,
|
||||
size_t raw_len,
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
const char *density,
|
||||
const char *warning) {
|
||||
char submit_err[128] = {0};
|
||||
uint32_t job_id = 0;
|
||||
esp_err_t submit_rc = printer_protocol_submit_raster_job(raw,
|
||||
raw_len,
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
&job_id,
|
||||
submit_err,
|
||||
sizeof(submit_err));
|
||||
if (submit_rc != ESP_OK) {
|
||||
return rest_server_send_error(req,
|
||||
"400 Bad Request",
|
||||
submit_err[0] != '\0' ? submit_err : "submit failed");
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "ok", true);
|
||||
cJSON_AddNumberToObject(root, "job_id", job_id);
|
||||
cJSON_AddStringToObject(root, "state", "queued");
|
||||
if (warning != NULL && warning[0] != '\0') {
|
||||
cJSON_AddStringToObject(root, "warning", warning);
|
||||
}
|
||||
|
||||
esp_err_t err = rest_server_send_json(req, "202 Accepted", root);
|
||||
cJSON_Delete(root);
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t decode_image_json_to_raster(cJSON *json,
|
||||
uint16_t *out_width,
|
||||
uint16_t *out_height,
|
||||
uint8_t **out_raster,
|
||||
size_t *out_len,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (json == NULL || out_width == NULL || out_height == NULL || out_raster == NULL || out_len == NULL) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid args");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
*out_width = 0;
|
||||
*out_height = 0;
|
||||
*out_raster = NULL;
|
||||
*out_len = 0;
|
||||
|
||||
cJSON *jwidth = cJSON_GetObjectItemCaseSensitive(json, "width");
|
||||
cJSON *jheight = cJSON_GetObjectItemCaseSensitive(json, "height");
|
||||
cJSON *jencoding = cJSON_GetObjectItemCaseSensitive(json, "encoding");
|
||||
cJSON *jdata = cJSON_GetObjectItemCaseSensitive(json, "data");
|
||||
|
||||
if (!cJSON_IsNumber(jwidth) || !cJSON_IsNumber(jheight) ||
|
||||
!cJSON_IsString(jencoding) || !cJSON_IsString(jdata)) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "missing required image fields");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
uint16_t width = (uint16_t)jwidth->valuedouble;
|
||||
uint16_t height = (uint16_t)jheight->valuedouble;
|
||||
if (width == 0 || height == 0) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid image size");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (strcmp(jencoding->valuestring, "base64_msb_1bpp") == 0) {
|
||||
uint8_t *raw = NULL;
|
||||
size_t raw_len = 0;
|
||||
esp_err_t b64_rc = rest_server_base64_decode_alloc(jdata->valuestring, &raw, &raw_len);
|
||||
if (b64_rc != ESP_OK) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid base64");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
*out_width = width;
|
||||
*out_height = height;
|
||||
*out_raster = raw;
|
||||
*out_len = raw_len;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (strcmp(jencoding->valuestring, "base64_gray8") == 0) {
|
||||
uint8_t *gray = NULL;
|
||||
size_t gray_len = 0;
|
||||
esp_err_t b64_rc = rest_server_base64_decode_alloc(jdata->valuestring, &gray, &gray_len);
|
||||
if (b64_rc != ESP_OK) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid base64");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
size_t expected = (size_t)width * height;
|
||||
if (gray_len != expected) {
|
||||
free(gray);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "gray8 size mismatch");
|
||||
}
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "scale_to_width");
|
||||
cJSON *jthreshold = cJSON_GetObjectItemCaseSensitive(json, "threshold");
|
||||
cJSON *jinvert = cJSON_GetObjectItemCaseSensitive(json, "invert");
|
||||
cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height");
|
||||
|
||||
bool scale_to_width = rest_server_json_bool_with_default(jscale, true);
|
||||
bool invert = rest_server_json_bool_with_default(jinvert, false);
|
||||
uint8_t threshold = 160;
|
||||
uint16_t max_height = 2200;
|
||||
if (cJSON_IsNumber(jthreshold) && jthreshold->valuedouble >= 0 && jthreshold->valuedouble <= 255) {
|
||||
threshold = (uint8_t)jthreshold->valuedouble;
|
||||
}
|
||||
if (cJSON_IsNumber(jmaxh) && jmaxh->valuedouble >= 64 && jmaxh->valuedouble <= 3000) {
|
||||
max_height = (uint16_t)jmaxh->valuedouble;
|
||||
}
|
||||
|
||||
uint16_t out_w = 0;
|
||||
uint16_t out_h = 0;
|
||||
uint8_t *raster = NULL;
|
||||
size_t raster_len = 0;
|
||||
esp_err_t conv_rc = raster_tools_convert_gray8_to_raster_384(gray,
|
||||
width,
|
||||
height,
|
||||
scale_to_width,
|
||||
threshold,
|
||||
invert,
|
||||
max_height,
|
||||
&out_w,
|
||||
&out_h,
|
||||
&raster,
|
||||
&raster_len,
|
||||
err,
|
||||
err_len);
|
||||
free(gray);
|
||||
if (conv_rc != ESP_OK) {
|
||||
return conv_rc;
|
||||
}
|
||||
|
||||
*out_width = out_w;
|
||||
*out_height = out_h;
|
||||
*out_raster = raster;
|
||||
*out_len = raster_len;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "unsupported encoding");
|
||||
}
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_print_raster_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jwidth = cJSON_GetObjectItemCaseSensitive(json, "width");
|
||||
cJSON *jheight = cJSON_GetObjectItemCaseSensitive(json, "height");
|
||||
cJSON *jencoding = cJSON_GetObjectItemCaseSensitive(json, "encoding");
|
||||
cJSON *jdata = cJSON_GetObjectItemCaseSensitive(json, "data");
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
|
||||
if (!cJSON_IsNumber(jwidth) || !cJSON_IsNumber(jheight) ||
|
||||
!cJSON_IsString(jencoding) || !cJSON_IsString(jdata)) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "missing required fields");
|
||||
}
|
||||
|
||||
if (strcmp(jencoding->valuestring, "base64_msb_1bpp") != 0) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "unsupported encoding");
|
||||
}
|
||||
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
|
||||
uint8_t *raw = NULL;
|
||||
size_t raw_len = 0;
|
||||
esp_err_t b64_rc = rest_server_base64_decode_alloc(jdata->valuestring, &raw, &raw_len);
|
||||
if (b64_rc != ESP_OK) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid base64");
|
||||
}
|
||||
|
||||
esp_err_t submit_rc = submit_raster_job_and_reply(req,
|
||||
raw,
|
||||
raw_len,
|
||||
(uint16_t)jwidth->valuedouble,
|
||||
(uint16_t)jheight->valuedouble,
|
||||
density,
|
||||
NULL);
|
||||
free(raw);
|
||||
cJSON_Delete(json);
|
||||
return submit_rc;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_print_image_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
uint8_t *raster = NULL;
|
||||
size_t raster_len = 0;
|
||||
char decode_err[128] = {0};
|
||||
esp_err_t decode_rc = decode_image_json_to_raster(json,
|
||||
&width,
|
||||
&height,
|
||||
&raster,
|
||||
&raster_len,
|
||||
decode_err,
|
||||
sizeof(decode_err));
|
||||
cJSON_Delete(json);
|
||||
if (decode_rc != ESP_OK) {
|
||||
return rest_server_send_error(req,
|
||||
"400 Bad Request",
|
||||
decode_err[0] != '\0' ? decode_err : "decode image failed");
|
||||
}
|
||||
|
||||
esp_err_t submit_rc = submit_raster_job_and_reply(req,
|
||||
raster,
|
||||
raster_len,
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
NULL);
|
||||
free(raster);
|
||||
return submit_rc;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_print_qr_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jtext = cJSON_GetObjectItemCaseSensitive(json, "text");
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
cJSON *jecc = cJSON_GetObjectItemCaseSensitive(json, "ecc");
|
||||
cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "module_scale");
|
||||
cJSON *jmargin = cJSON_GetObjectItemCaseSensitive(json, "margin_modules");
|
||||
cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height");
|
||||
|
||||
if (!cJSON_IsString(jtext) || jtext->valuestring == NULL || jtext->valuestring[0] == '\0') {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "text is required");
|
||||
}
|
||||
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
|
||||
uint8_t ecc_level = 3; /* default H */
|
||||
if (cJSON_IsString(jecc) && jecc->valuestring != NULL) {
|
||||
if (strcasecmp(jecc->valuestring, "L") == 0) {
|
||||
ecc_level = 0;
|
||||
} else if (strcasecmp(jecc->valuestring, "M") == 0) {
|
||||
ecc_level = 1;
|
||||
} else if (strcasecmp(jecc->valuestring, "Q") == 0) {
|
||||
ecc_level = 2;
|
||||
} else if (strcasecmp(jecc->valuestring, "H") == 0) {
|
||||
ecc_level = 3;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t module_scale = 0;
|
||||
uint8_t margin_modules = 2;
|
||||
uint16_t max_height = 2200;
|
||||
if (cJSON_IsNumber(jscale) && jscale->valuedouble >= 0 && jscale->valuedouble <= 16) {
|
||||
module_scale = (uint8_t)jscale->valuedouble;
|
||||
}
|
||||
if (cJSON_IsNumber(jmargin) && jmargin->valuedouble >= 1 && jmargin->valuedouble <= 12) {
|
||||
margin_modules = (uint8_t)jmargin->valuedouble;
|
||||
}
|
||||
if (cJSON_IsNumber(jmaxh) && jmaxh->valuedouble >= 64 && jmaxh->valuedouble <= 3000) {
|
||||
max_height = (uint16_t)jmaxh->valuedouble;
|
||||
}
|
||||
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
uint8_t *raster = NULL;
|
||||
size_t raster_len = 0;
|
||||
char render_msg[128] = {0};
|
||||
esp_err_t qr_rc = raster_tools_render_qr_384(jtext->valuestring,
|
||||
module_scale,
|
||||
margin_modules,
|
||||
ecc_level,
|
||||
max_height,
|
||||
&width,
|
||||
&height,
|
||||
&raster,
|
||||
&raster_len,
|
||||
render_msg,
|
||||
sizeof(render_msg));
|
||||
cJSON_Delete(json);
|
||||
if (qr_rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", render_msg[0] != '\0' ? render_msg : "qr render failed");
|
||||
}
|
||||
|
||||
esp_err_t submit_rc = submit_raster_job_and_reply(req,
|
||||
raster,
|
||||
raster_len,
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
NULL);
|
||||
free(raster);
|
||||
return submit_rc;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_print_label_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
cJSON *jgap = cJSON_GetObjectItemCaseSensitive(json, "gap_move_before");
|
||||
cJSON *joffset = cJSON_GetObjectItemCaseSensitive(json, "offset_tenths_mm");
|
||||
|
||||
bool gap_move_before = rest_server_json_bool_with_default(jgap, true);
|
||||
bool has_offset = cJSON_IsNumber(joffset);
|
||||
uint8_t offset_value = 0;
|
||||
if (has_offset) {
|
||||
if (joffset->valuedouble < 0 || joffset->valuedouble > 254) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "offset_tenths_mm must be 0..254");
|
||||
}
|
||||
offset_value = (uint8_t)joffset->valuedouble;
|
||||
}
|
||||
|
||||
if (has_offset) {
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t offset_rc = printer_protocol_set_label_offset(offset_value, 3000, cmd_err, sizeof(cmd_err));
|
||||
if (offset_rc != ESP_OK) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req,
|
||||
"409 Conflict",
|
||||
cmd_err[0] != '\0' ? cmd_err : "set label offset failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (gap_move_before) {
|
||||
char cmd_err[128] = {0};
|
||||
esp_err_t gap_rc = printer_protocol_gap_move(5000, cmd_err, sizeof(cmd_err));
|
||||
if (gap_rc != ESP_OK) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req,
|
||||
"409 Conflict",
|
||||
cmd_err[0] != '\0' ? cmd_err : "gap move failed");
|
||||
}
|
||||
}
|
||||
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
uint8_t *raster = NULL;
|
||||
size_t raster_len = 0;
|
||||
char decode_err[128] = {0};
|
||||
esp_err_t decode_rc = decode_image_json_to_raster(json,
|
||||
&width,
|
||||
&height,
|
||||
&raster,
|
||||
&raster_len,
|
||||
decode_err,
|
||||
sizeof(decode_err));
|
||||
cJSON_Delete(json);
|
||||
if (decode_rc != ESP_OK) {
|
||||
return rest_server_send_error(req,
|
||||
"400 Bad Request",
|
||||
decode_err[0] != '\0' ? decode_err : "decode image failed");
|
||||
}
|
||||
|
||||
esp_err_t submit_rc = submit_raster_job_and_reply(req,
|
||||
raster,
|
||||
raster_len,
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
NULL);
|
||||
free(raster);
|
||||
return submit_rc;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_print_text_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jtext = cJSON_GetObjectItemCaseSensitive(json, "text");
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "scale");
|
||||
cJSON *jline = cJSON_GetObjectItemCaseSensitive(json, "line_spacing");
|
||||
cJSON *jmaxh = cJSON_GetObjectItemCaseSensitive(json, "max_height");
|
||||
|
||||
if (!cJSON_IsString(jtext) || jtext->valuestring == NULL) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "400 Bad Request", "text is required");
|
||||
}
|
||||
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
uint8_t scale = 2;
|
||||
uint8_t line_spacing = 2;
|
||||
uint16_t max_height = 2000;
|
||||
|
||||
if (cJSON_IsNumber(jscale) && jscale->valuedouble >= 1 && jscale->valuedouble <= 6) {
|
||||
scale = (uint8_t)jscale->valuedouble;
|
||||
}
|
||||
if (cJSON_IsNumber(jline) && jline->valuedouble >= 0 && jline->valuedouble <= 12) {
|
||||
line_spacing = (uint8_t)jline->valuedouble;
|
||||
}
|
||||
if (cJSON_IsNumber(jmaxh) && jmaxh->valuedouble >= 64 && jmaxh->valuedouble <= 3000) {
|
||||
max_height = (uint16_t)jmaxh->valuedouble;
|
||||
}
|
||||
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
uint8_t *raster = NULL;
|
||||
size_t raster_len = 0;
|
||||
char render_msg[128] = {0};
|
||||
|
||||
esp_err_t render_rc = raster_tools_render_text_384(jtext->valuestring,
|
||||
scale,
|
||||
line_spacing,
|
||||
max_height,
|
||||
&width,
|
||||
&height,
|
||||
&raster,
|
||||
&raster_len,
|
||||
render_msg,
|
||||
sizeof(render_msg));
|
||||
cJSON_Delete(json);
|
||||
|
||||
if (render_rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", render_msg[0] != '\0' ? render_msg : "text render failed");
|
||||
}
|
||||
|
||||
esp_err_t submit_rc = submit_raster_job_and_reply(req,
|
||||
raster,
|
||||
raster_len,
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
render_msg);
|
||||
free(raster);
|
||||
return submit_rc;
|
||||
}
|
||||
|
||||
esp_err_t rest_server_print_receipt_post(httpd_req_t *req) {
|
||||
if (!rest_server_auth_ok(req)) {
|
||||
return rest_server_send_error(req, "401 Unauthorized", "unauthorized");
|
||||
}
|
||||
|
||||
char *body = NULL;
|
||||
esp_err_t body_err = rest_server_read_body(req, &body);
|
||||
if (body_err != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid request body");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(body);
|
||||
free(body);
|
||||
if (json == NULL) {
|
||||
return rest_server_send_error(req, "400 Bad Request", "invalid json");
|
||||
}
|
||||
|
||||
cJSON *jtitle = cJSON_GetObjectItemCaseSensitive(json, "title");
|
||||
cJSON *jitems = cJSON_GetObjectItemCaseSensitive(json, "items");
|
||||
cJSON *jfooter = cJSON_GetObjectItemCaseSensitive(json, "footer");
|
||||
cJSON *jdensity = cJSON_GetObjectItemCaseSensitive(json, "density");
|
||||
cJSON *jscale = cJSON_GetObjectItemCaseSensitive(json, "scale");
|
||||
|
||||
const char *title = (cJSON_IsString(jtitle) && jtitle->valuestring != NULL)
|
||||
? jtitle->valuestring
|
||||
: "LYF RECEIPT";
|
||||
const char *footer = (cJSON_IsString(jfooter) && jfooter->valuestring != NULL)
|
||||
? jfooter->valuestring
|
||||
: "Thank you";
|
||||
const char *density = (cJSON_IsString(jdensity) && jdensity->valuestring != NULL)
|
||||
? jdensity->valuestring
|
||||
: "中等";
|
||||
uint8_t scale = 2;
|
||||
if (cJSON_IsNumber(jscale) && jscale->valuedouble >= 1 && jscale->valuedouble <= 4) {
|
||||
scale = (uint8_t)jscale->valuedouble;
|
||||
}
|
||||
|
||||
size_t text_cap = 8192;
|
||||
char *text = (char *)calloc(1, text_cap);
|
||||
if (text == NULL) {
|
||||
cJSON_Delete(json);
|
||||
return rest_server_send_error(req, "500 Internal Server Error", "no memory");
|
||||
}
|
||||
|
||||
size_t off = 0;
|
||||
rest_server_appendf(text, text_cap, &off, "%s\n", title);
|
||||
rest_server_appendf(text, text_cap, &off, "--------------------------------\n");
|
||||
|
||||
double total = 0.0;
|
||||
if (cJSON_IsArray(jitems)) {
|
||||
int n = cJSON_GetArraySize(jitems);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
cJSON *it = cJSON_GetArrayItem(jitems, i);
|
||||
if (!cJSON_IsObject(it)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON *jname = cJSON_GetObjectItemCaseSensitive(it, "name");
|
||||
cJSON *jqty = cJSON_GetObjectItemCaseSensitive(it, "qty");
|
||||
cJSON *jprice = cJSON_GetObjectItemCaseSensitive(it, "price");
|
||||
|
||||
const char *name = (cJSON_IsString(jname) && jname->valuestring != NULL)
|
||||
? jname->valuestring
|
||||
: "item";
|
||||
int qty = cJSON_IsNumber(jqty) ? (int)jqty->valuedouble : 1;
|
||||
double price = cJSON_IsNumber(jprice) ? jprice->valuedouble : 0.0;
|
||||
double line_total = qty * price;
|
||||
total += line_total;
|
||||
|
||||
rest_server_appendf(text,
|
||||
text_cap,
|
||||
&off,
|
||||
"%-16.16s x%-3d %8.2f\n",
|
||||
name,
|
||||
qty,
|
||||
line_total);
|
||||
}
|
||||
} else {
|
||||
rest_server_appendf(text, text_cap, &off, "(no items)\n");
|
||||
}
|
||||
|
||||
rest_server_appendf(text, text_cap, &off, "--------------------------------\n");
|
||||
rest_server_appendf(text, text_cap, &off, "TOTAL: %.2f\n", total);
|
||||
rest_server_appendf(text, text_cap, &off, "%s\n", footer);
|
||||
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
uint8_t *raster = NULL;
|
||||
size_t raster_len = 0;
|
||||
char render_msg[128] = {0};
|
||||
|
||||
esp_err_t render_rc = raster_tools_render_text_384(text,
|
||||
scale,
|
||||
2,
|
||||
2200,
|
||||
&width,
|
||||
&height,
|
||||
&raster,
|
||||
&raster_len,
|
||||
render_msg,
|
||||
sizeof(render_msg));
|
||||
free(text);
|
||||
cJSON_Delete(json);
|
||||
|
||||
if (render_rc != ESP_OK) {
|
||||
return rest_server_send_error(req, "400 Bad Request", render_msg[0] != '\0' ? render_msg : "receipt render failed");
|
||||
}
|
||||
|
||||
esp_err_t submit_rc = submit_raster_job_and_reply(req,
|
||||
raster,
|
||||
raster_len,
|
||||
width,
|
||||
height,
|
||||
density,
|
||||
render_msg);
|
||||
free(raster);
|
||||
return submit_rc;
|
||||
}
|
||||
Reference in New Issue
Block a user