init
This commit is contained in:
9
main/domain/README.md
Normal file
9
main/domain/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Domain Layer
|
||||
|
||||
Purpose: business capabilities with single responsibility per domain service.
|
||||
|
||||
Rules:
|
||||
- Expose stable domain APIs in `include/`.
|
||||
- Keep implementation details in `internal/` and `src/`.
|
||||
- No direct cross-domain internal state access.
|
||||
- Cross-domain interactions must use events or Port interfaces.
|
||||
BIN
main/domain/assets/fonts/cn16_glyphs.bin
Normal file
BIN
main/domain/assets/fonts/cn16_glyphs.bin
Normal file
Binary file not shown.
BIN
main/domain/assets/fonts/cn16_index.bin
Normal file
BIN
main/domain/assets/fonts/cn16_index.bin
Normal file
Binary file not shown.
0
main/domain/include/.gitkeep
Normal file
0
main/domain/include/.gitkeep
Normal file
90
main/domain/include/printer_protocol.h
Normal file
90
main/domain/include/printer_protocol.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
typedef enum {
|
||||
PRINT_JOB_STATE_NONE = 0,
|
||||
PRINT_JOB_STATE_QUEUED,
|
||||
PRINT_JOB_STATE_RUNNING,
|
||||
PRINT_JOB_STATE_SUCCESS,
|
||||
PRINT_JOB_STATE_FAILED,
|
||||
PRINT_JOB_STATE_CANCELED,
|
||||
} print_job_state_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
print_job_state_t state;
|
||||
uint8_t progress;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
size_t data_len;
|
||||
char density[16];
|
||||
char error[96];
|
||||
int64_t created_ms;
|
||||
int64_t started_ms;
|
||||
int64_t finished_ms;
|
||||
} print_job_info_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t major;
|
||||
uint8_t minor;
|
||||
uint8_t patch;
|
||||
} printer_ota_version_t;
|
||||
|
||||
typedef struct {
|
||||
bool connected;
|
||||
bool notify_ready;
|
||||
bool busy;
|
||||
bool has_paper;
|
||||
uint8_t battery_percent;
|
||||
float temperature;
|
||||
uint16_t mtu;
|
||||
uint32_t queue_depth;
|
||||
int64_t last_status_ms;
|
||||
} printer_runtime_status_t;
|
||||
|
||||
esp_err_t printer_protocol_init(void);
|
||||
|
||||
esp_err_t printer_protocol_connect(const char *target_name, uint32_t timeout_ms);
|
||||
void printer_protocol_disconnect(void);
|
||||
|
||||
void printer_protocol_get_runtime_status(printer_runtime_status_t *out_status);
|
||||
|
||||
esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster,
|
||||
size_t raster_len,
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
const char *density,
|
||||
uint32_t *out_job_id,
|
||||
char *err,
|
||||
size_t err_len);
|
||||
|
||||
bool printer_protocol_get_job(uint32_t job_id, print_job_info_t *out_info);
|
||||
esp_err_t printer_protocol_list_jobs(print_job_info_t *out_jobs, size_t max_jobs, size_t *out_count);
|
||||
esp_err_t printer_protocol_cancel_job(uint32_t job_id, char *err, size_t err_len);
|
||||
size_t printer_protocol_cleanup_jobs(bool include_success, bool include_failed, bool include_canceled);
|
||||
|
||||
esp_err_t printer_protocol_gap_move(uint32_t timeout_ms, char *err, size_t err_len);
|
||||
esp_err_t printer_protocol_get_label_offset(uint8_t *out_offset, uint32_t timeout_ms, char *err, size_t err_len);
|
||||
esp_err_t printer_protocol_set_label_offset(uint8_t offset, uint32_t timeout_ms, char *err, size_t err_len);
|
||||
|
||||
esp_err_t printer_protocol_ota_jump_boot(uint32_t timeout_ms, char *err, size_t err_len);
|
||||
esp_err_t printer_protocol_ota_jump_app(uint32_t timeout_ms, char *err, size_t err_len);
|
||||
esp_err_t printer_protocol_ota_erase_page(uint16_t page_num, uint32_t timeout_ms, char *err, size_t err_len);
|
||||
esp_err_t printer_protocol_ota_write_frame(uint16_t packet_num,
|
||||
bool is_last_frame,
|
||||
const uint8_t *data,
|
||||
size_t data_len,
|
||||
uint32_t timeout_ms,
|
||||
char *err,
|
||||
size_t err_len);
|
||||
esp_err_t printer_protocol_ota_get_version(printer_ota_version_t *out_version,
|
||||
uint32_t timeout_ms,
|
||||
char *err,
|
||||
size_t err_len);
|
||||
|
||||
const char *printer_protocol_job_state_str(print_job_state_t state);
|
||||
44
main/domain/include/raster_tools.h
Normal file
44
main/domain/include/raster_tools.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
esp_err_t raster_tools_render_text_384(const char *text,
|
||||
uint8_t scale,
|
||||
uint8_t line_spacing,
|
||||
uint16_t max_height,
|
||||
uint16_t *out_width,
|
||||
uint16_t *out_height,
|
||||
uint8_t **out_raster,
|
||||
size_t *out_len,
|
||||
char *err,
|
||||
size_t err_len);
|
||||
|
||||
esp_err_t raster_tools_convert_gray8_to_raster_384(const uint8_t *gray,
|
||||
uint16_t src_width,
|
||||
uint16_t src_height,
|
||||
bool scale_to_width,
|
||||
uint8_t threshold,
|
||||
bool invert,
|
||||
uint16_t max_height,
|
||||
uint16_t *out_width,
|
||||
uint16_t *out_height,
|
||||
uint8_t **out_raster,
|
||||
size_t *out_len,
|
||||
char *err,
|
||||
size_t err_len);
|
||||
|
||||
esp_err_t raster_tools_render_qr_384(const char *text,
|
||||
uint8_t module_scale,
|
||||
uint8_t margin_modules,
|
||||
uint8_t ecc_level,
|
||||
uint16_t max_height,
|
||||
uint16_t *out_width,
|
||||
uint16_t *out_height,
|
||||
uint8_t **out_raster,
|
||||
size_t *out_len,
|
||||
char *err,
|
||||
size_t err_len);
|
||||
11
main/domain/include/system_runtime.h
Normal file
11
main/domain/include/system_runtime.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
esp_err_t system_runtime_bootstrap(void);
|
||||
|
||||
bool system_runtime_wifi_ready(void);
|
||||
void system_runtime_get_ip(char *buf, size_t buf_len);
|
||||
0
main/domain/internal/.gitkeep
Normal file
0
main/domain/internal/.gitkeep
Normal file
32
main/domain/internal/printer_protocol_internal.h
Normal file
32
main/domain/internal/printer_protocol_internal.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#define CMD_GAP_MOVE 0x05
|
||||
#define CMD_GET_OFFSET 0x06
|
||||
#define CMD_SET_OFFSET 0x07
|
||||
#define CMD_BOOT_JUMP_BOOT 0xA0
|
||||
#define CMD_BOOT_ERASE_PAGE 0xA1
|
||||
#define CMD_BOOT_WRITE_DATA 0xA2
|
||||
#define CMD_BOOT_JUMP_APP 0xA3
|
||||
#define CMD_BOOT_GET_VERSION 0xA4
|
||||
|
||||
#define OTA_MAX_DATA_PER_FRAME 236u
|
||||
|
||||
bool printer_protocol_acquire_control_lane(uint32_t timeout_ms, char *err, size_t err_len);
|
||||
void printer_protocol_release_control_lane(void);
|
||||
esp_err_t printer_protocol_send_cmd_wait_response(uint8_t cmd,
|
||||
const uint8_t *payload,
|
||||
uint16_t payload_len,
|
||||
bool with_checksum,
|
||||
uint32_t timeout_ms,
|
||||
bool expect_ack,
|
||||
uint8_t *out_payload,
|
||||
size_t out_payload_cap,
|
||||
uint16_t *out_payload_len,
|
||||
char *err,
|
||||
size_t err_len);
|
||||
0
main/domain/src/.gitkeep
Normal file
0
main/domain/src/.gitkeep
Normal file
931
main/domain/src/printer_protocol.c
Normal file
931
main/domain/src/printer_protocol.c
Normal file
@@ -0,0 +1,931 @@
|
||||
#include "printer_protocol.h"
|
||||
#include "printer_protocol_internal.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ble_printer_client.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define PROTO_ADDR 0x01
|
||||
|
||||
#define CMD_POWER 0x00
|
||||
#define CMD_GET_STATUS 0x01
|
||||
#define CMD_SET_DISTANCE 0x02
|
||||
#define CMD_SET_PARAM 0x03
|
||||
#define CMD_SEND_DATA 0x04
|
||||
|
||||
#define EVT_ACK BIT0
|
||||
|
||||
#define JOB_QUEUE_LEN 8
|
||||
#define JOB_SLOT_MAX 16
|
||||
#define MAX_RASTER_BYTES (384 * 3000 / 8)
|
||||
|
||||
typedef struct {
|
||||
bool used;
|
||||
bool cancel_requested;
|
||||
uint32_t id;
|
||||
print_job_state_t state;
|
||||
uint8_t progress;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
size_t data_len;
|
||||
char density[16];
|
||||
char error[96];
|
||||
int64_t created_ms;
|
||||
int64_t started_ms;
|
||||
int64_t finished_ms;
|
||||
uint8_t *data;
|
||||
} job_slot_t;
|
||||
|
||||
typedef struct {
|
||||
bool has_paper;
|
||||
uint8_t battery;
|
||||
float temperature;
|
||||
int64_t updated_ms;
|
||||
} parsed_status_t;
|
||||
|
||||
static SemaphoreHandle_t s_mutex;
|
||||
static QueueHandle_t s_job_queue;
|
||||
static EventGroupHandle_t s_evt;
|
||||
|
||||
static uint32_t s_busy_refcnt;
|
||||
|
||||
static parsed_status_t s_status = {
|
||||
.has_paper = true,
|
||||
.battery = 100,
|
||||
.temperature = 25.0f,
|
||||
.updated_ms = 0,
|
||||
};
|
||||
|
||||
static uint8_t s_last_rsp_cmd;
|
||||
static uint16_t s_last_rsp_payload_len;
|
||||
static uint8_t s_last_rsp_payload[252];
|
||||
|
||||
static uint32_t s_next_job_id = 1;
|
||||
static job_slot_t s_jobs[JOB_SLOT_MAX];
|
||||
|
||||
static int find_job_idx_locked(uint32_t id) {
|
||||
for (int i = 0; i < JOB_SLOT_MAX; ++i) {
|
||||
if (s_jobs[i].used && s_jobs[i].id == id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int alloc_job_slot_locked(void) {
|
||||
for (int i = 0; i < JOB_SLOT_MAX; ++i) {
|
||||
if (!s_jobs[i].used) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reuse completed slot when table is full. */
|
||||
for (int i = 0; i < JOB_SLOT_MAX; ++i) {
|
||||
if (s_jobs[i].state == PRINT_JOB_STATE_SUCCESS ||
|
||||
s_jobs[i].state == PRINT_JOB_STATE_FAILED ||
|
||||
s_jobs[i].state == PRINT_JOB_STATE_CANCELED) {
|
||||
free(s_jobs[i].data);
|
||||
memset(&s_jobs[i], 0, sizeof(s_jobs[i]));
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool is_terminal_state(print_job_state_t state) {
|
||||
return state == PRINT_JOB_STATE_SUCCESS ||
|
||||
state == PRINT_JOB_STATE_FAILED ||
|
||||
state == PRINT_JOB_STATE_CANCELED;
|
||||
}
|
||||
|
||||
bool printer_protocol_acquire_control_lane(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "lock timeout");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s_busy_refcnt != 0) {
|
||||
xSemaphoreGive(s_mutex);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer busy");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
s_busy_refcnt = 1;
|
||||
xSemaphoreGive(s_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
void printer_protocol_release_control_lane(void) {
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_busy_refcnt > 0) {
|
||||
--s_busy_refcnt;
|
||||
}
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
|
||||
static uint8_t checksum8(const uint8_t *data, size_t len) {
|
||||
uint32_t sum = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
sum += data[i];
|
||||
}
|
||||
return (uint8_t)(sum & 0xFF);
|
||||
}
|
||||
|
||||
static esp_err_t send_frame(uint8_t cmd, const uint8_t *payload, uint16_t payload_len, bool with_checksum) {
|
||||
uint8_t frame[260];
|
||||
size_t total = 4 + payload_len + (with_checksum ? 1 : 0);
|
||||
if (total > sizeof(frame)) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
frame[0] = PROTO_ADDR;
|
||||
frame[1] = cmd;
|
||||
frame[2] = (uint8_t)((payload_len >> 8) & 0xFF);
|
||||
frame[3] = (uint8_t)(payload_len & 0xFF);
|
||||
|
||||
if (payload_len > 0 && payload != NULL) {
|
||||
memcpy(&frame[4], payload, payload_len);
|
||||
}
|
||||
|
||||
if (with_checksum) {
|
||||
frame[4 + payload_len] = checksum8(frame, 4 + payload_len);
|
||||
}
|
||||
|
||||
return ble_printer_client_write(frame, total);
|
||||
}
|
||||
|
||||
static void clear_ack_signal(void) {
|
||||
xEventGroupClearBits(s_evt, EVT_ACK);
|
||||
}
|
||||
|
||||
static bool wait_response(uint8_t cmd,
|
||||
uint8_t *out_payload,
|
||||
size_t out_payload_cap,
|
||||
uint16_t *out_payload_len,
|
||||
uint32_t timeout_ms) {
|
||||
int64_t deadline = esp_timer_get_time() / 1000 + timeout_ms;
|
||||
|
||||
while (true) {
|
||||
int64_t now = esp_timer_get_time() / 1000;
|
||||
if (now >= deadline) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t wait_ms = (uint32_t)(deadline - now);
|
||||
EventBits_t bits = xEventGroupWaitBits(s_evt,
|
||||
EVT_ACK,
|
||||
pdTRUE,
|
||||
pdFALSE,
|
||||
pdMS_TO_TICKS(wait_ms));
|
||||
if (!(bits & EVT_ACK)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t rsp_cmd = s_last_rsp_cmd;
|
||||
uint16_t rsp_len = s_last_rsp_payload_len;
|
||||
uint8_t rsp_copy[sizeof(s_last_rsp_payload)];
|
||||
if (rsp_len > sizeof(rsp_copy)) {
|
||||
rsp_len = sizeof(rsp_copy);
|
||||
}
|
||||
if (rsp_len > 0) {
|
||||
memcpy(rsp_copy, s_last_rsp_payload, rsp_len);
|
||||
}
|
||||
xSemaphoreGive(s_mutex);
|
||||
|
||||
if (rsp_cmd == cmd) {
|
||||
if (out_payload != NULL && out_payload_cap > 0 && rsp_len > 0) {
|
||||
size_t copy_len = rsp_len;
|
||||
if (copy_len > out_payload_cap) {
|
||||
copy_len = out_payload_cap;
|
||||
}
|
||||
memcpy(out_payload, rsp_copy, copy_len);
|
||||
}
|
||||
if (out_payload_len != NULL) {
|
||||
*out_payload_len = rsp_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool wait_ack(uint8_t cmd, uint32_t timeout_ms) {
|
||||
uint8_t payload[252];
|
||||
uint16_t payload_len = 0;
|
||||
if (!wait_response(cmd, payload, sizeof(payload), &payload_len, timeout_ms)) {
|
||||
return false;
|
||||
}
|
||||
return payload_len >= 1 && payload[0] == 0x01;
|
||||
}
|
||||
|
||||
static bool send_cmd_with_ack(uint8_t cmd,
|
||||
const uint8_t *payload,
|
||||
uint16_t payload_len,
|
||||
bool with_checksum,
|
||||
uint32_t timeout_ms) {
|
||||
clear_ack_signal();
|
||||
if (send_frame(cmd, payload, payload_len, with_checksum) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
return wait_ack(cmd, timeout_ms);
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_send_cmd_wait_response(uint8_t cmd,
|
||||
const uint8_t *payload,
|
||||
uint16_t payload_len,
|
||||
bool with_checksum,
|
||||
uint32_t timeout_ms,
|
||||
bool expect_ack,
|
||||
uint8_t *out_payload,
|
||||
size_t out_payload_cap,
|
||||
uint16_t *out_payload_len,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
clear_ack_signal();
|
||||
if (send_frame(cmd, payload, payload_len, with_checksum) != ESP_OK) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "send frame failed");
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
uint8_t rsp[252];
|
||||
uint16_t rsp_len = 0;
|
||||
if (!wait_response(cmd, rsp, sizeof(rsp), &rsp_len, timeout_ms)) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "response timeout");
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (expect_ack && (rsp_len < 1 || rsp[0] != 0x01)) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "cmd 0x%02X rejected", cmd);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (out_payload != NULL && out_payload_cap > 0 && rsp_len > 0) {
|
||||
size_t copy_len = rsp_len;
|
||||
if (copy_len > out_payload_cap) {
|
||||
copy_len = out_payload_cap;
|
||||
}
|
||||
memcpy(out_payload, rsp, copy_len);
|
||||
}
|
||||
if (out_payload_len != NULL) {
|
||||
*out_payload_len = rsp_len;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static bool request_status_sync(uint32_t timeout_ms) {
|
||||
int64_t old_ms;
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
|
||||
return false;
|
||||
}
|
||||
old_ms = s_status.updated_ms;
|
||||
xSemaphoreGive(s_mutex);
|
||||
|
||||
uint8_t payload = 0x00;
|
||||
if (send_frame(CMD_GET_STATUS, &payload, 0, true) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t deadline = esp_timer_get_time() / 1000 + timeout_ms;
|
||||
while ((esp_timer_get_time() / 1000) < deadline) {
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool updated = s_status.updated_ms > old_ms;
|
||||
xSemaphoreGive(s_mutex);
|
||||
|
||||
if (updated) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint16_t density_to_hot_time(const char *density) {
|
||||
if (density == NULL) {
|
||||
return 2000;
|
||||
}
|
||||
if (strcmp(density, "较淡") == 0) {
|
||||
return 1000;
|
||||
}
|
||||
if (strcmp(density, "中等") == 0) {
|
||||
return 1500;
|
||||
}
|
||||
if (strcmp(density, "较浓") == 0) {
|
||||
return 2000;
|
||||
}
|
||||
if (strcmp(density, "最深") == 0) {
|
||||
return 3000;
|
||||
}
|
||||
return 2000;
|
||||
}
|
||||
|
||||
static bool precheck_printer_ready(char *err, size_t err_len) {
|
||||
if (!request_status_sync(1200)) {
|
||||
snprintf(err, err_len, "status timeout");
|
||||
return false;
|
||||
}
|
||||
|
||||
parsed_status_t status;
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) != pdTRUE) {
|
||||
snprintf(err, err_len, "status lock failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = s_status;
|
||||
xSemaphoreGive(s_mutex);
|
||||
|
||||
if (!status.has_paper) {
|
||||
snprintf(err, err_len, "printer out of paper");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status.battery <= 40) {
|
||||
snprintf(err, err_len, "battery too low");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status.temperature >= 60.0f) {
|
||||
snprintf(err, err_len, "temperature too high");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool run_print_job(job_slot_t *job) {
|
||||
char err[96];
|
||||
err[0] = '\0';
|
||||
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
snprintf(job->error, sizeof(job->error), "printer not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!precheck_printer_ready(err, sizeof(err))) {
|
||||
snprintf(job->error, sizeof(job->error), "%s", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (job->cancel_requested) {
|
||||
snprintf(job->error, sizeof(job->error), "job canceled");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t power_on = 0x01;
|
||||
if (!send_cmd_with_ack(CMD_POWER, &power_on, 1, true, 1500)) {
|
||||
snprintf(job->error, sizeof(job->error), "power on failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t hot_time = density_to_hot_time(job->density);
|
||||
uint8_t param[4] = {
|
||||
0x01,
|
||||
0x02,
|
||||
(uint8_t)((hot_time >> 8) & 0xFF),
|
||||
(uint8_t)(hot_time & 0xFF),
|
||||
};
|
||||
|
||||
if (!send_cmd_with_ack(CMD_SET_PARAM, param, sizeof(param), true, 1500)) {
|
||||
snprintf(job->error, sizeof(job->error), "set print param failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t chunk_max = 240;
|
||||
size_t total_chunks = (size_t)ceil((double)job->data_len / (double)chunk_max);
|
||||
|
||||
for (size_t i = 0; i < total_chunks; ++i) {
|
||||
if (job->cancel_requested) {
|
||||
snprintf(job->error, sizeof(job->error), "job canceled");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t start = i * chunk_max;
|
||||
size_t remain = job->data_len - start;
|
||||
size_t chunk_len = remain > chunk_max ? chunk_max : remain;
|
||||
|
||||
if (!send_cmd_with_ack(CMD_SEND_DATA,
|
||||
&job->data[start],
|
||||
(uint16_t)chunk_len,
|
||||
false,
|
||||
2500)) {
|
||||
snprintf(job->error, sizeof(job->error), "send chunk timeout at %u", (unsigned)i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
||||
job->progress = (uint8_t)(((i + 1) * 90) / total_chunks);
|
||||
if (job->progress < 5) {
|
||||
job->progress = 5;
|
||||
}
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t power_off = 0x00;
|
||||
(void)send_cmd_with_ack(CMD_POWER, &power_off, 1, true, 1200);
|
||||
|
||||
uint8_t feed_payload[3] = {0x2B, 0x00, 0x0C};
|
||||
(void)send_cmd_with_ack(CMD_SET_DISTANCE, feed_payload, sizeof(feed_payload), true, 1200);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void worker_task(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
while (true) {
|
||||
uint32_t job_id;
|
||||
if (xQueueReceive(s_job_queue, &job_id, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int idx = find_job_idx_locked(job_id);
|
||||
if (idx < 0) {
|
||||
xSemaphoreGive(s_mutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s_jobs[idx].state == PRINT_JOB_STATE_CANCELED) {
|
||||
free(s_jobs[idx].data);
|
||||
s_jobs[idx].data = NULL;
|
||||
if (s_jobs[idx].finished_ms == 0) {
|
||||
s_jobs[idx].finished_ms = esp_timer_get_time() / 1000;
|
||||
}
|
||||
xSemaphoreGive(s_mutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s_busy_refcnt != 0) {
|
||||
xSemaphoreGive(s_mutex);
|
||||
xQueueSendToFront(s_job_queue, &job_id, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
continue;
|
||||
}
|
||||
|
||||
s_jobs[idx].state = PRINT_JOB_STATE_RUNNING;
|
||||
s_jobs[idx].started_ms = esp_timer_get_time() / 1000;
|
||||
s_jobs[idx].progress = 1;
|
||||
s_busy_refcnt = 1;
|
||||
xSemaphoreGive(s_mutex);
|
||||
|
||||
bool ok = run_print_job(&s_jobs[idx]);
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s_busy_refcnt > 0) {
|
||||
--s_busy_refcnt;
|
||||
}
|
||||
s_jobs[idx].finished_ms = esp_timer_get_time() / 1000;
|
||||
if (s_jobs[idx].state != PRINT_JOB_STATE_CANCELED) {
|
||||
s_jobs[idx].progress = 100;
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
s_jobs[idx].state = PRINT_JOB_STATE_SUCCESS;
|
||||
s_jobs[idx].error[0] = '\0';
|
||||
} else if (s_jobs[idx].cancel_requested) {
|
||||
s_jobs[idx].state = PRINT_JOB_STATE_CANCELED;
|
||||
if (s_jobs[idx].error[0] == '\0') {
|
||||
strlcpy(s_jobs[idx].error, "job canceled", sizeof(s_jobs[idx].error));
|
||||
}
|
||||
} else {
|
||||
s_jobs[idx].state = PRINT_JOB_STATE_FAILED;
|
||||
}
|
||||
|
||||
free(s_jobs[idx].data);
|
||||
s_jobs[idx].data = NULL;
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static void status_poll_task(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
while (true) {
|
||||
bool can_poll = false;
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
|
||||
can_poll = (s_busy_refcnt == 0);
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
|
||||
if (ble_printer_client_is_connected() && can_poll) {
|
||||
uint8_t dummy = 0x00;
|
||||
(void)send_frame(CMD_GET_STATUS, &dummy, 0, true);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
}
|
||||
|
||||
static void on_rx_frame(const uint8_t *data, size_t len) {
|
||||
if (data == NULL || len < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data[0] != PROTO_ADDR) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t cmd = data[1];
|
||||
uint16_t payload_len = ((uint16_t)data[2] << 8) | data[3];
|
||||
size_t required = (size_t)payload_len + 4;
|
||||
if (len < required) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t *payload = &data[4];
|
||||
|
||||
if (cmd == CMD_GET_STATUS && payload_len >= 5) {
|
||||
bool has_paper = payload[0] != 0;
|
||||
uint8_t battery = payload[1];
|
||||
int sign = (payload[2] == 0x2D) ? -1 : 1;
|
||||
int temp_x10 = ((int)payload[3] << 8) | payload[4];
|
||||
float temperature = (float)(sign * temp_x10) / 10.0f;
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
s_status.has_paper = has_paper;
|
||||
s_status.battery = battery;
|
||||
s_status.temperature = temperature;
|
||||
s_status.updated_ms = esp_timer_get_time() / 1000;
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload_len >= 1) {
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
s_last_rsp_cmd = cmd;
|
||||
s_last_rsp_payload_len = payload_len;
|
||||
if (s_last_rsp_payload_len > sizeof(s_last_rsp_payload)) {
|
||||
s_last_rsp_payload_len = sizeof(s_last_rsp_payload);
|
||||
}
|
||||
memcpy(s_last_rsp_payload, payload, s_last_rsp_payload_len);
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
xEventGroupSetBits(s_evt, EVT_ACK);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_init(void) {
|
||||
s_mutex = xSemaphoreCreateMutex();
|
||||
if (s_mutex == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
s_evt = xEventGroupCreate();
|
||||
if (s_evt == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
s_job_queue = xQueueCreate(JOB_QUEUE_LEN, sizeof(uint32_t));
|
||||
if (s_job_queue == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_err_t err = ble_printer_client_init(on_rx_frame);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
BaseType_t ok = xTaskCreate(worker_task, "print_worker", 6144, NULL, 6, NULL);
|
||||
if (ok != pdPASS) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
ok = xTaskCreate(status_poll_task, "status_poll", 4096, NULL, 4, NULL);
|
||||
if (ok != pdPASS) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_connect(const char *target_name, uint32_t timeout_ms) {
|
||||
return ble_printer_client_connect(target_name, timeout_ms);
|
||||
}
|
||||
|
||||
void printer_protocol_disconnect(void) {
|
||||
ble_printer_client_disconnect();
|
||||
}
|
||||
|
||||
void printer_protocol_get_runtime_status(printer_runtime_status_t *out_status) {
|
||||
if (out_status == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(out_status, 0, sizeof(*out_status));
|
||||
|
||||
ble_link_state_t link = {0};
|
||||
ble_printer_client_get_link_state(&link);
|
||||
|
||||
out_status->connected = link.connected;
|
||||
out_status->notify_ready = link.notify_ready;
|
||||
out_status->mtu = link.mtu;
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
||||
out_status->busy = s_busy_refcnt > 0;
|
||||
out_status->has_paper = s_status.has_paper;
|
||||
out_status->battery_percent = s_status.battery;
|
||||
out_status->temperature = s_status.temperature;
|
||||
out_status->last_status_ms = s_status.updated_ms;
|
||||
out_status->queue_depth = (uint32_t)uxQueueMessagesWaiting(s_job_queue);
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_submit_raster_job(const uint8_t *raster,
|
||||
size_t raster_len,
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
const char *density,
|
||||
uint32_t *out_job_id,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (raster == NULL || raster_len == 0 || width == 0 || height == 0) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid args");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (width != 384) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "width must be 384");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (raster_len > MAX_RASTER_BYTES) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "raster too large");
|
||||
}
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
size_t expected_len = ((size_t)width / 8u) * (size_t)height;
|
||||
if (expected_len != raster_len) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "size mismatch exp=%u got=%u",
|
||||
(unsigned)expected_len,
|
||||
(unsigned)raster_len);
|
||||
}
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "lock timeout");
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
int idx = alloc_job_slot_locked();
|
||||
if (idx < 0) {
|
||||
xSemaphoreGive(s_mutex);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "job table full");
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
uint8_t *copy = (uint8_t *)malloc(raster_len);
|
||||
if (copy == NULL) {
|
||||
xSemaphoreGive(s_mutex);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "malloc failed");
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
memcpy(copy, raster, raster_len);
|
||||
|
||||
uint32_t id = s_next_job_id++;
|
||||
if (s_next_job_id == 0) {
|
||||
s_next_job_id = 1;
|
||||
}
|
||||
|
||||
memset(&s_jobs[idx], 0, sizeof(s_jobs[idx]));
|
||||
s_jobs[idx].used = true;
|
||||
s_jobs[idx].id = id;
|
||||
s_jobs[idx].state = PRINT_JOB_STATE_QUEUED;
|
||||
s_jobs[idx].progress = 0;
|
||||
s_jobs[idx].width = width;
|
||||
s_jobs[idx].height = height;
|
||||
s_jobs[idx].data_len = raster_len;
|
||||
s_jobs[idx].created_ms = esp_timer_get_time() / 1000;
|
||||
s_jobs[idx].data = copy;
|
||||
strlcpy(s_jobs[idx].density, density != NULL ? density : "中等", sizeof(s_jobs[idx].density));
|
||||
|
||||
xSemaphoreGive(s_mutex);
|
||||
|
||||
if (xQueueSend(s_job_queue, &id, pdMS_TO_TICKS(500)) != pdTRUE) {
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {
|
||||
int rollback_idx = find_job_idx_locked(id);
|
||||
if (rollback_idx >= 0) {
|
||||
free(s_jobs[rollback_idx].data);
|
||||
memset(&s_jobs[rollback_idx], 0, sizeof(s_jobs[rollback_idx]));
|
||||
}
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "job queue full");
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (out_job_id != NULL) {
|
||||
*out_job_id = id;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool printer_protocol_get_job(uint32_t job_id, print_job_info_t *out_info) {
|
||||
if (out_info == NULL || job_id == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) != pdTRUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int idx = find_job_idx_locked(job_id);
|
||||
if (idx < 0) {
|
||||
xSemaphoreGive(s_mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(out_info, 0, sizeof(*out_info));
|
||||
|
||||
out_info->id = s_jobs[idx].id;
|
||||
out_info->state = s_jobs[idx].state;
|
||||
out_info->progress = s_jobs[idx].progress;
|
||||
out_info->width = s_jobs[idx].width;
|
||||
out_info->height = s_jobs[idx].height;
|
||||
out_info->data_len = s_jobs[idx].data_len;
|
||||
out_info->created_ms = s_jobs[idx].created_ms;
|
||||
out_info->started_ms = s_jobs[idx].started_ms;
|
||||
out_info->finished_ms = s_jobs[idx].finished_ms;
|
||||
strlcpy(out_info->density, s_jobs[idx].density, sizeof(out_info->density));
|
||||
strlcpy(out_info->error, s_jobs[idx].error, sizeof(out_info->error));
|
||||
|
||||
xSemaphoreGive(s_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_list_jobs(print_job_info_t *out_jobs, size_t max_jobs, size_t *out_count) {
|
||||
if (out_count == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) != pdTRUE) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
size_t count = 0;
|
||||
for (int i = 0; i < JOB_SLOT_MAX; ++i) {
|
||||
if (!s_jobs[i].used || s_jobs[i].state == PRINT_JOB_STATE_NONE) {
|
||||
continue;
|
||||
}
|
||||
if (out_jobs != NULL && count < max_jobs) {
|
||||
memset(&out_jobs[count], 0, sizeof(out_jobs[count]));
|
||||
out_jobs[count].id = s_jobs[i].id;
|
||||
out_jobs[count].state = s_jobs[i].state;
|
||||
out_jobs[count].progress = s_jobs[i].progress;
|
||||
out_jobs[count].width = s_jobs[i].width;
|
||||
out_jobs[count].height = s_jobs[i].height;
|
||||
out_jobs[count].data_len = s_jobs[i].data_len;
|
||||
out_jobs[count].created_ms = s_jobs[i].created_ms;
|
||||
out_jobs[count].started_ms = s_jobs[i].started_ms;
|
||||
out_jobs[count].finished_ms = s_jobs[i].finished_ms;
|
||||
strlcpy(out_jobs[count].density, s_jobs[i].density, sizeof(out_jobs[count].density));
|
||||
strlcpy(out_jobs[count].error, s_jobs[i].error, sizeof(out_jobs[count].error));
|
||||
}
|
||||
++count;
|
||||
}
|
||||
|
||||
xSemaphoreGive(s_mutex);
|
||||
*out_count = count;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_cancel_job(uint32_t job_id, char *err, size_t err_len) {
|
||||
if (job_id == 0) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid job id");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) != pdTRUE) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "lock timeout");
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
int idx = find_job_idx_locked(job_id);
|
||||
if (idx < 0) {
|
||||
xSemaphoreGive(s_mutex);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "job not found");
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (is_terminal_state(s_jobs[idx].state)) {
|
||||
xSemaphoreGive(s_mutex);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "job already finished");
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
s_jobs[idx].cancel_requested = true;
|
||||
if (s_jobs[idx].state == PRINT_JOB_STATE_QUEUED) {
|
||||
s_jobs[idx].state = PRINT_JOB_STATE_CANCELED;
|
||||
s_jobs[idx].finished_ms = esp_timer_get_time() / 1000;
|
||||
s_jobs[idx].progress = 0;
|
||||
strlcpy(s_jobs[idx].error, "job canceled", sizeof(s_jobs[idx].error));
|
||||
free(s_jobs[idx].data);
|
||||
s_jobs[idx].data = NULL;
|
||||
} else if (s_jobs[idx].state == PRINT_JOB_STATE_RUNNING) {
|
||||
strlcpy(s_jobs[idx].error, "cancel requested", sizeof(s_jobs[idx].error));
|
||||
}
|
||||
|
||||
xSemaphoreGive(s_mutex);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
size_t printer_protocol_cleanup_jobs(bool include_success, bool include_failed, bool include_canceled) {
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(500)) != pdTRUE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t removed = 0;
|
||||
for (int i = 0; i < JOB_SLOT_MAX; ++i) {
|
||||
if (!s_jobs[i].used) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
if (include_success && s_jobs[i].state == PRINT_JOB_STATE_SUCCESS) {
|
||||
match = true;
|
||||
}
|
||||
if (include_failed && s_jobs[i].state == PRINT_JOB_STATE_FAILED) {
|
||||
match = true;
|
||||
}
|
||||
if (include_canceled && s_jobs[i].state == PRINT_JOB_STATE_CANCELED) {
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
free(s_jobs[i].data);
|
||||
memset(&s_jobs[i], 0, sizeof(s_jobs[i]));
|
||||
++removed;
|
||||
}
|
||||
|
||||
xSemaphoreGive(s_mutex);
|
||||
return removed;
|
||||
}
|
||||
322
main/domain/src/printer_protocol_commands.c
Normal file
322
main/domain/src/printer_protocol_commands.c
Normal file
@@ -0,0 +1,322 @@
|
||||
#include "printer_protocol.h"
|
||||
#include "printer_protocol_internal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ble_printer_client.h"
|
||||
|
||||
esp_err_t printer_protocol_gap_move(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!printer_protocol_acquire_control_lane(1000, err, err_len)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t payload = 0x01;
|
||||
esp_err_t rc = printer_protocol_send_cmd_wait_response(CMD_GAP_MOVE,
|
||||
&payload,
|
||||
1,
|
||||
true,
|
||||
timeout_ms,
|
||||
true,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
err,
|
||||
err_len);
|
||||
printer_protocol_release_control_lane();
|
||||
return rc;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_get_label_offset(uint8_t *out_offset, uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (out_offset == NULL) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid args");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!printer_protocol_acquire_control_lane(1000, err, err_len)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t req = 0x01;
|
||||
uint8_t rsp[16];
|
||||
uint16_t rsp_len = 0;
|
||||
esp_err_t rc = printer_protocol_send_cmd_wait_response(CMD_GET_OFFSET,
|
||||
&req,
|
||||
1,
|
||||
true,
|
||||
timeout_ms,
|
||||
false,
|
||||
rsp,
|
||||
sizeof(rsp),
|
||||
&rsp_len,
|
||||
err,
|
||||
err_len);
|
||||
printer_protocol_release_control_lane();
|
||||
if (rc != ESP_OK) {
|
||||
return rc;
|
||||
}
|
||||
if (rsp_len < 1) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid offset response");
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (rsp[0] == 0xFF) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "offset unavailable");
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
*out_offset = rsp[0];
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_set_label_offset(uint8_t offset, uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!printer_protocol_acquire_control_lane(1000, err, err_len)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t rc = printer_protocol_send_cmd_wait_response(CMD_SET_OFFSET,
|
||||
&offset,
|
||||
1,
|
||||
true,
|
||||
timeout_ms,
|
||||
true,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
err,
|
||||
err_len);
|
||||
printer_protocol_release_control_lane();
|
||||
return rc;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_ota_jump_boot(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!printer_protocol_acquire_control_lane(1000, err, err_len)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t rc = printer_protocol_send_cmd_wait_response(CMD_BOOT_JUMP_BOOT,
|
||||
NULL,
|
||||
0,
|
||||
true,
|
||||
timeout_ms,
|
||||
true,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
err,
|
||||
err_len);
|
||||
printer_protocol_release_control_lane();
|
||||
return rc;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_ota_jump_app(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!printer_protocol_acquire_control_lane(1000, err, err_len)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t rc = printer_protocol_send_cmd_wait_response(CMD_BOOT_JUMP_APP,
|
||||
NULL,
|
||||
0,
|
||||
true,
|
||||
timeout_ms,
|
||||
true,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
err,
|
||||
err_len);
|
||||
printer_protocol_release_control_lane();
|
||||
return rc;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_ota_erase_page(uint16_t page_num, uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!printer_protocol_acquire_control_lane(1000, err, err_len)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t payload[2] = {
|
||||
(uint8_t)((page_num >> 8) & 0xFF),
|
||||
(uint8_t)(page_num & 0xFF),
|
||||
};
|
||||
|
||||
esp_err_t rc = printer_protocol_send_cmd_wait_response(CMD_BOOT_ERASE_PAGE,
|
||||
payload,
|
||||
sizeof(payload),
|
||||
true,
|
||||
timeout_ms,
|
||||
true,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
err,
|
||||
err_len);
|
||||
printer_protocol_release_control_lane();
|
||||
return rc;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_ota_write_frame(uint16_t packet_num,
|
||||
bool is_last_frame,
|
||||
const uint8_t *data,
|
||||
size_t data_len,
|
||||
uint32_t timeout_ms,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if ((data_len > 0 && data == NULL) || data_len > OTA_MAX_DATA_PER_FRAME) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid frame length");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!printer_protocol_acquire_control_lane(1000, err, err_len)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t payload[3 + OTA_MAX_DATA_PER_FRAME];
|
||||
payload[0] = (uint8_t)((packet_num >> 8) & 0xFF);
|
||||
payload[1] = (uint8_t)(packet_num & 0xFF);
|
||||
payload[2] = is_last_frame ? 0x01 : 0x00;
|
||||
if (data_len > 0) {
|
||||
memcpy(&payload[3], data, data_len);
|
||||
}
|
||||
|
||||
esp_err_t rc = printer_protocol_send_cmd_wait_response(CMD_BOOT_WRITE_DATA,
|
||||
payload,
|
||||
(uint16_t)(3 + data_len),
|
||||
true,
|
||||
timeout_ms,
|
||||
true,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
err,
|
||||
err_len);
|
||||
printer_protocol_release_control_lane();
|
||||
return rc;
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_ota_get_version(printer_ota_version_t *out_version,
|
||||
uint32_t timeout_ms,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (out_version == NULL) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid args");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!ble_printer_client_is_connected()) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "printer not connected");
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!printer_protocol_acquire_control_lane(1000, err, err_len)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t rsp[16];
|
||||
uint16_t rsp_len = 0;
|
||||
esp_err_t rc = printer_protocol_send_cmd_wait_response(CMD_BOOT_GET_VERSION,
|
||||
NULL,
|
||||
0,
|
||||
true,
|
||||
timeout_ms,
|
||||
false,
|
||||
rsp,
|
||||
sizeof(rsp),
|
||||
&rsp_len,
|
||||
err,
|
||||
err_len);
|
||||
printer_protocol_release_control_lane();
|
||||
if (rc != ESP_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (rsp_len < 3) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid version response");
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
out_version->major = rsp[0];
|
||||
out_version->minor = rsp[1];
|
||||
out_version->patch = rsp[2];
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const char *printer_protocol_job_state_str(print_job_state_t state) {
|
||||
switch (state) {
|
||||
case PRINT_JOB_STATE_NONE:
|
||||
return "none";
|
||||
case PRINT_JOB_STATE_QUEUED:
|
||||
return "queued";
|
||||
case PRINT_JOB_STATE_RUNNING:
|
||||
return "running";
|
||||
case PRINT_JOB_STATE_SUCCESS:
|
||||
return "success";
|
||||
case PRINT_JOB_STATE_FAILED:
|
||||
return "failed";
|
||||
case PRINT_JOB_STATE_CANCELED:
|
||||
return "canceled";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
467
main/domain/src/raster_tools.c
Normal file
467
main/domain/src/raster_tools.c
Normal file
@@ -0,0 +1,467 @@
|
||||
#include "raster_tools.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define RASTER_WIDTH 384
|
||||
#define RASTER_BYTES_PER_ROW (RASTER_WIDTH / 8)
|
||||
#define CN16_GLYPH_BYTES 32u
|
||||
#define CN16_INDEX_ENTRY_BYTES 6u
|
||||
#define UTF8_REPLACEMENT_CODEPOINT 0x003Fu
|
||||
|
||||
extern const uint8_t _binary_cn16_index_bin_start[] asm("_binary_cn16_index_bin_start");
|
||||
extern const uint8_t _binary_cn16_index_bin_end[] asm("_binary_cn16_index_bin_end");
|
||||
extern const uint8_t _binary_cn16_glyphs_bin_start[] asm("_binary_cn16_glyphs_bin_start");
|
||||
extern const uint8_t _binary_cn16_glyphs_bin_end[] asm("_binary_cn16_glyphs_bin_end");
|
||||
|
||||
/* 5x7 ASCII font table for characters 0x20..0x7E. */
|
||||
static const uint8_t font5x7[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, /* space */
|
||||
0x00, 0x00, 0x5F, 0x00, 0x00, /* ! */
|
||||
0x00, 0x07, 0x00, 0x07, 0x00, /* " */
|
||||
0x14, 0x7F, 0x14, 0x7F, 0x14, /* # */
|
||||
0x24, 0x2A, 0x7F, 0x2A, 0x12, /* $ */
|
||||
0x23, 0x13, 0x08, 0x64, 0x62, /* % */
|
||||
0x36, 0x49, 0x55, 0x22, 0x50, /* & */
|
||||
0x00, 0x05, 0x03, 0x00, 0x00, /* ' */
|
||||
0x00, 0x1C, 0x22, 0x41, 0x00, /* ( */
|
||||
0x00, 0x41, 0x22, 0x1C, 0x00, /* ) */
|
||||
0x14, 0x08, 0x3E, 0x08, 0x14, /* * */
|
||||
0x08, 0x08, 0x3E, 0x08, 0x08, /* + */
|
||||
0x00, 0x50, 0x30, 0x00, 0x00, /* , */
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, /* - */
|
||||
0x00, 0x60, 0x60, 0x00, 0x00, /* . */
|
||||
0x20, 0x10, 0x08, 0x04, 0x02, /* / */
|
||||
0x3E, 0x51, 0x49, 0x45, 0x3E, /* 0 */
|
||||
0x00, 0x42, 0x7F, 0x40, 0x00, /* 1 */
|
||||
0x42, 0x61, 0x51, 0x49, 0x46, /* 2 */
|
||||
0x21, 0x41, 0x45, 0x4B, 0x31, /* 3 */
|
||||
0x18, 0x14, 0x12, 0x7F, 0x10, /* 4 */
|
||||
0x27, 0x45, 0x45, 0x45, 0x39, /* 5 */
|
||||
0x3C, 0x4A, 0x49, 0x49, 0x30, /* 6 */
|
||||
0x01, 0x71, 0x09, 0x05, 0x03, /* 7 */
|
||||
0x36, 0x49, 0x49, 0x49, 0x36, /* 8 */
|
||||
0x06, 0x49, 0x49, 0x29, 0x1E, /* 9 */
|
||||
0x00, 0x36, 0x36, 0x00, 0x00, /* : */
|
||||
0x00, 0x56, 0x36, 0x00, 0x00, /* ; */
|
||||
0x08, 0x14, 0x22, 0x41, 0x00, /* < */
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, /* = */
|
||||
0x00, 0x41, 0x22, 0x14, 0x08, /* > */
|
||||
0x02, 0x01, 0x51, 0x09, 0x06, /* ? */
|
||||
0x32, 0x49, 0x79, 0x41, 0x3E, /* @ */
|
||||
0x7E, 0x11, 0x11, 0x11, 0x7E, /* A */
|
||||
0x7F, 0x49, 0x49, 0x49, 0x36, /* B */
|
||||
0x3E, 0x41, 0x41, 0x41, 0x22, /* C */
|
||||
0x7F, 0x41, 0x41, 0x22, 0x1C, /* D */
|
||||
0x7F, 0x49, 0x49, 0x49, 0x41, /* E */
|
||||
0x7F, 0x09, 0x09, 0x09, 0x01, /* F */
|
||||
0x3E, 0x41, 0x49, 0x49, 0x7A, /* G */
|
||||
0x7F, 0x08, 0x08, 0x08, 0x7F, /* H */
|
||||
0x00, 0x41, 0x7F, 0x41, 0x00, /* I */
|
||||
0x20, 0x40, 0x41, 0x3F, 0x01, /* J */
|
||||
0x7F, 0x08, 0x14, 0x22, 0x41, /* K */
|
||||
0x7F, 0x40, 0x40, 0x40, 0x40, /* L */
|
||||
0x7F, 0x02, 0x0C, 0x02, 0x7F, /* M */
|
||||
0x7F, 0x04, 0x08, 0x10, 0x7F, /* N */
|
||||
0x3E, 0x41, 0x41, 0x41, 0x3E, /* O */
|
||||
0x7F, 0x09, 0x09, 0x09, 0x06, /* P */
|
||||
0x3E, 0x41, 0x51, 0x21, 0x5E, /* Q */
|
||||
0x7F, 0x09, 0x19, 0x29, 0x46, /* R */
|
||||
0x46, 0x49, 0x49, 0x49, 0x31, /* S */
|
||||
0x01, 0x01, 0x7F, 0x01, 0x01, /* T */
|
||||
0x3F, 0x40, 0x40, 0x40, 0x3F, /* U */
|
||||
0x1F, 0x20, 0x40, 0x20, 0x1F, /* V */
|
||||
0x7F, 0x20, 0x18, 0x20, 0x7F, /* W */
|
||||
0x63, 0x14, 0x08, 0x14, 0x63, /* X */
|
||||
0x03, 0x04, 0x78, 0x04, 0x03, /* Y */
|
||||
0x61, 0x51, 0x49, 0x45, 0x43, /* Z */
|
||||
0x00, 0x00, 0x7F, 0x41, 0x41, /* [ */
|
||||
0x02, 0x04, 0x08, 0x10, 0x20, /* \\ */
|
||||
0x41, 0x41, 0x7F, 0x00, 0x00, /* ] */
|
||||
0x04, 0x02, 0x01, 0x02, 0x04, /* ^ */
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, /* _ */
|
||||
0x00, 0x03, 0x05, 0x00, 0x00, /* ` */
|
||||
0x20, 0x54, 0x54, 0x54, 0x78, /* a */
|
||||
0x7F, 0x48, 0x44, 0x44, 0x38, /* b */
|
||||
0x38, 0x44, 0x44, 0x44, 0x20, /* c */
|
||||
0x38, 0x44, 0x44, 0x48, 0x7F, /* d */
|
||||
0x38, 0x54, 0x54, 0x54, 0x18, /* e */
|
||||
0x08, 0x7E, 0x09, 0x01, 0x02, /* f */
|
||||
0x0C, 0x52, 0x52, 0x52, 0x3E, /* g */
|
||||
0x7F, 0x08, 0x04, 0x04, 0x78, /* h */
|
||||
0x00, 0x44, 0x7D, 0x40, 0x00, /* i */
|
||||
0x20, 0x40, 0x44, 0x3D, 0x00, /* j */
|
||||
0x7F, 0x10, 0x28, 0x44, 0x00, /* k */
|
||||
0x00, 0x41, 0x7F, 0x40, 0x00, /* l */
|
||||
0x7C, 0x04, 0x18, 0x04, 0x78, /* m */
|
||||
0x7C, 0x08, 0x04, 0x04, 0x78, /* n */
|
||||
0x38, 0x44, 0x44, 0x44, 0x38, /* o */
|
||||
0x7C, 0x14, 0x14, 0x14, 0x08, /* p */
|
||||
0x08, 0x14, 0x14, 0x18, 0x7C, /* q */
|
||||
0x7C, 0x08, 0x04, 0x04, 0x08, /* r */
|
||||
0x48, 0x54, 0x54, 0x54, 0x20, /* s */
|
||||
0x04, 0x3F, 0x44, 0x40, 0x20, /* t */
|
||||
0x3C, 0x40, 0x40, 0x20, 0x7C, /* u */
|
||||
0x1C, 0x20, 0x40, 0x20, 0x1C, /* v */
|
||||
0x3C, 0x40, 0x30, 0x40, 0x3C, /* w */
|
||||
0x44, 0x28, 0x10, 0x28, 0x44, /* x */
|
||||
0x0C, 0x50, 0x50, 0x50, 0x3C, /* y */
|
||||
0x44, 0x64, 0x54, 0x4C, 0x44, /* z */
|
||||
0x00, 0x08, 0x36, 0x41, 0x00, /* { */
|
||||
0x00, 0x00, 0x7F, 0x00, 0x00, /* | */
|
||||
0x00, 0x41, 0x36, 0x08, 0x00, /* } */
|
||||
0x08, 0x08, 0x2A, 0x1C, 0x08 /* ~ */
|
||||
};
|
||||
|
||||
static void set_black(uint8_t *buf, uint16_t width, uint16_t x, uint16_t y) {
|
||||
if (x >= width) {
|
||||
return;
|
||||
}
|
||||
uint16_t bpr = width / 8;
|
||||
size_t idx = (size_t)y * bpr + (x / 8);
|
||||
uint8_t bit = (uint8_t)(7 - (x % 8));
|
||||
buf[idx] |= (uint8_t)(1u << bit);
|
||||
}
|
||||
|
||||
static size_t cn16_index_count(void) {
|
||||
size_t bytes = (size_t)(_binary_cn16_index_bin_end - _binary_cn16_index_bin_start);
|
||||
return bytes / CN16_INDEX_ENTRY_BYTES;
|
||||
}
|
||||
|
||||
static bool cn16_read_index_entry(size_t idx, uint32_t *out_cp, uint16_t *out_gid) {
|
||||
if (out_cp == NULL || out_gid == NULL) {
|
||||
return false;
|
||||
}
|
||||
size_t count = cn16_index_count();
|
||||
if (idx >= count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *p = _binary_cn16_index_bin_start + idx * CN16_INDEX_ENTRY_BYTES;
|
||||
*out_cp = ((uint32_t)p[0]) |
|
||||
((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) |
|
||||
((uint32_t)p[3] << 24);
|
||||
*out_gid = (uint16_t)(p[4] | ((uint16_t)p[5] << 8));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cn16_lookup_glyph(uint32_t codepoint, const uint8_t **out_glyph) {
|
||||
if (out_glyph == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t count = cn16_index_count();
|
||||
if (count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t lo = 0;
|
||||
size_t hi = count;
|
||||
while (lo < hi) {
|
||||
size_t mid = lo + ((hi - lo) / 2);
|
||||
uint32_t cp = 0;
|
||||
uint16_t gid = 0;
|
||||
if (!cn16_read_index_entry(mid, &cp, &gid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cp == codepoint) {
|
||||
size_t glyph_bytes = (size_t)(_binary_cn16_glyphs_bin_end -
|
||||
_binary_cn16_glyphs_bin_start);
|
||||
size_t off = (size_t)gid * CN16_GLYPH_BYTES;
|
||||
if ((off + CN16_GLYPH_BYTES) > glyph_bytes) {
|
||||
return false;
|
||||
}
|
||||
*out_glyph = _binary_cn16_glyphs_bin_start + off;
|
||||
return true;
|
||||
}
|
||||
if (cp < codepoint) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void draw_char(uint8_t *buf,
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
uint16_t x,
|
||||
uint16_t y,
|
||||
char ch,
|
||||
uint8_t scale) {
|
||||
if (ch < 32 || ch > 126) {
|
||||
ch = '?';
|
||||
}
|
||||
|
||||
const uint8_t *glyph = &font5x7[(ch - 32) * 5];
|
||||
for (uint8_t col = 0; col < 5; ++col) {
|
||||
uint8_t bits = glyph[col];
|
||||
for (uint8_t row = 0; row < 7; ++row) {
|
||||
if (((bits >> row) & 0x01u) == 0) {
|
||||
continue;
|
||||
}
|
||||
for (uint8_t sx = 0; sx < scale; ++sx) {
|
||||
for (uint8_t sy = 0; sy < scale; ++sy) {
|
||||
uint16_t px = (uint16_t)(x + col * scale + sx);
|
||||
uint16_t py = (uint16_t)(y + row * scale + sy);
|
||||
if (py < height) {
|
||||
set_black(buf, width, px, py);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_cn16_glyph(uint8_t *buf,
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
uint16_t x,
|
||||
uint16_t y,
|
||||
const uint8_t *glyph,
|
||||
uint8_t scale) {
|
||||
if (glyph == NULL || scale == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t row = 0; row < 16; ++row) {
|
||||
uint16_t bits = ((uint16_t)glyph[row * 2] << 8) | glyph[row * 2 + 1];
|
||||
for (uint8_t col = 0; col < 16; ++col) {
|
||||
if ((bits & (uint16_t)(1u << (15 - col))) == 0) {
|
||||
continue;
|
||||
}
|
||||
for (uint8_t sx = 0; sx < scale; ++sx) {
|
||||
for (uint8_t sy = 0; sy < scale; ++sy) {
|
||||
uint16_t px = (uint16_t)(x + col * scale + sx);
|
||||
uint16_t py = (uint16_t)(y + row * scale + sy);
|
||||
if (py < height && px < width) {
|
||||
set_black(buf, width, px, py);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_missing_box(uint8_t *buf,
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
uint16_t x,
|
||||
uint16_t y,
|
||||
uint8_t scale) {
|
||||
uint16_t box = (uint16_t)(16 * scale);
|
||||
for (uint16_t r = 0; r < box; ++r) {
|
||||
for (uint16_t c = 0; c < box; ++c) {
|
||||
bool edge = (r < scale) || (c < scale) || (r >= box - scale) || (c >= box - scale);
|
||||
if (!edge) {
|
||||
continue;
|
||||
}
|
||||
uint16_t px = (uint16_t)(x + c);
|
||||
uint16_t py = (uint16_t)(y + r);
|
||||
if (px < width && py < height) {
|
||||
set_black(buf, width, px, py);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t utf8_decode_one(const unsigned char *s, size_t len, size_t *out_consumed) {
|
||||
if (out_consumed == NULL || s == NULL || len == 0) {
|
||||
return UTF8_REPLACEMENT_CODEPOINT;
|
||||
}
|
||||
|
||||
uint8_t b0 = s[0];
|
||||
if (b0 < 0x80) {
|
||||
*out_consumed = 1;
|
||||
return b0;
|
||||
}
|
||||
|
||||
if ((b0 & 0xE0u) == 0xC0u && len >= 2) {
|
||||
uint8_t b1 = s[1];
|
||||
if ((b1 & 0xC0u) == 0x80u) {
|
||||
uint32_t cp = ((uint32_t)(b0 & 0x1Fu) << 6) |
|
||||
(uint32_t)(b1 & 0x3Fu);
|
||||
if (cp >= 0x80u) {
|
||||
*out_consumed = 2;
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
} else if ((b0 & 0xF0u) == 0xE0u && len >= 3) {
|
||||
uint8_t b1 = s[1];
|
||||
uint8_t b2 = s[2];
|
||||
if ((b1 & 0xC0u) == 0x80u && (b2 & 0xC0u) == 0x80u) {
|
||||
uint32_t cp = ((uint32_t)(b0 & 0x0Fu) << 12) |
|
||||
((uint32_t)(b1 & 0x3Fu) << 6) |
|
||||
(uint32_t)(b2 & 0x3Fu);
|
||||
if (cp >= 0x800u && !(cp >= 0xD800u && cp <= 0xDFFFu)) {
|
||||
*out_consumed = 3;
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
} else if ((b0 & 0xF8u) == 0xF0u && len >= 4) {
|
||||
uint8_t b1 = s[1];
|
||||
uint8_t b2 = s[2];
|
||||
uint8_t b3 = s[3];
|
||||
if ((b1 & 0xC0u) == 0x80u && (b2 & 0xC0u) == 0x80u && (b3 & 0xC0u) == 0x80u) {
|
||||
uint32_t cp = ((uint32_t)(b0 & 0x07u) << 18) |
|
||||
((uint32_t)(b1 & 0x3Fu) << 12) |
|
||||
((uint32_t)(b2 & 0x3Fu) << 6) |
|
||||
(uint32_t)(b3 & 0x3Fu);
|
||||
if (cp >= 0x10000u && cp <= 0x10FFFFu) {
|
||||
*out_consumed = 4;
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*out_consumed = 1;
|
||||
return UTF8_REPLACEMENT_CODEPOINT;
|
||||
}
|
||||
|
||||
esp_err_t raster_tools_render_text_384(const char *text,
|
||||
uint8_t scale,
|
||||
uint8_t line_spacing,
|
||||
uint16_t max_height,
|
||||
uint16_t *out_width,
|
||||
uint16_t *out_height,
|
||||
uint8_t **out_raster,
|
||||
size_t *out_len,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (text == 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;
|
||||
}
|
||||
|
||||
if (scale == 0) {
|
||||
scale = 1;
|
||||
}
|
||||
|
||||
if (max_height == 0 || max_height > 3000) {
|
||||
max_height = 3000;
|
||||
}
|
||||
|
||||
size_t full_len = (size_t)RASTER_BYTES_PER_ROW * max_height;
|
||||
uint8_t *buf = (uint8_t *)calloc(1, full_len);
|
||||
if (buf == NULL) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "no memory");
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
uint16_t cursor_x = 0;
|
||||
uint16_t cursor_y = 0;
|
||||
uint16_t ascii_w = (uint16_t)(6 * scale);
|
||||
uint16_t ascii_h = (uint16_t)(7 * scale);
|
||||
uint16_t cjk_w = (uint16_t)(16 * scale);
|
||||
uint16_t cjk_h = (uint16_t)(16 * scale);
|
||||
uint16_t line_h = (uint16_t)(cjk_h + line_spacing * scale);
|
||||
if (line_h == 0) {
|
||||
line_h = cjk_h;
|
||||
}
|
||||
|
||||
uint16_t used_bottom = 0;
|
||||
bool truncated = false;
|
||||
|
||||
const unsigned char *p = (const unsigned char *)text;
|
||||
size_t remain = strlen(text);
|
||||
while (remain > 0) {
|
||||
size_t consumed = 0;
|
||||
uint32_t cp = utf8_decode_one(p, remain, &consumed);
|
||||
if (consumed == 0) {
|
||||
break;
|
||||
}
|
||||
p += consumed;
|
||||
remain -= consumed;
|
||||
|
||||
if (cp == '\r') {
|
||||
continue;
|
||||
}
|
||||
if (cp == '\n') {
|
||||
cursor_x = 0;
|
||||
cursor_y = (uint16_t)(cursor_y + line_h);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_ascii = cp >= 0x20u && cp <= 0x7Eu;
|
||||
uint16_t draw_w = is_ascii ? ascii_w : cjk_w;
|
||||
uint16_t draw_h = is_ascii ? ascii_h : cjk_h;
|
||||
uint16_t draw_y = cursor_y;
|
||||
if (is_ascii && cjk_h > ascii_h) {
|
||||
draw_y = (uint16_t)(cursor_y + (cjk_h - ascii_h) / 2);
|
||||
}
|
||||
|
||||
if ((uint16_t)(cursor_x + draw_w) > RASTER_WIDTH) {
|
||||
cursor_x = 0;
|
||||
cursor_y = (uint16_t)(cursor_y + line_h);
|
||||
draw_y = cursor_y;
|
||||
if (is_ascii && cjk_h > ascii_h) {
|
||||
draw_y = (uint16_t)(cursor_y + (cjk_h - ascii_h) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
if ((uint16_t)(draw_y + draw_h) > max_height) {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_ascii) {
|
||||
draw_char(buf, RASTER_WIDTH, max_height, cursor_x, draw_y, (char)cp, scale);
|
||||
} else {
|
||||
const uint8_t *glyph = NULL;
|
||||
if (cn16_lookup_glyph(cp, &glyph)) {
|
||||
draw_cn16_glyph(buf, RASTER_WIDTH, max_height, cursor_x, draw_y, glyph, scale);
|
||||
} else {
|
||||
draw_missing_box(buf, RASTER_WIDTH, max_height, cursor_x, draw_y, scale);
|
||||
}
|
||||
}
|
||||
cursor_x = (uint16_t)(cursor_x + draw_w);
|
||||
|
||||
uint16_t bottom = (uint16_t)(draw_y + draw_h);
|
||||
if (bottom > used_bottom) {
|
||||
used_bottom = bottom;
|
||||
}
|
||||
}
|
||||
|
||||
if (used_bottom == 0) {
|
||||
used_bottom = cjk_h;
|
||||
}
|
||||
|
||||
if (used_bottom > max_height) {
|
||||
used_bottom = max_height;
|
||||
}
|
||||
|
||||
size_t final_len = (size_t)RASTER_BYTES_PER_ROW * used_bottom;
|
||||
uint8_t *final_buf = (uint8_t *)malloc(final_len);
|
||||
if (final_buf == NULL) {
|
||||
free(buf);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "no memory");
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
memcpy(final_buf, buf, final_len);
|
||||
free(buf);
|
||||
|
||||
*out_width = RASTER_WIDTH;
|
||||
*out_height = used_bottom;
|
||||
*out_raster = final_buf;
|
||||
*out_len = final_len;
|
||||
|
||||
if (truncated && err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "text truncated due to max height");
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
258
main/domain/src/raster_tools_image_qr.c
Normal file
258
main/domain/src/raster_tools_image_qr.c
Normal file
@@ -0,0 +1,258 @@
|
||||
#include "raster_tools.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "qrcodegen.h"
|
||||
|
||||
#define RASTER_WIDTH 384
|
||||
#define RASTER_BYTES_PER_ROW (RASTER_WIDTH / 8)
|
||||
|
||||
static void set_black(uint8_t *buf, uint16_t width, uint16_t x, uint16_t y) {
|
||||
if (x >= width) {
|
||||
return;
|
||||
}
|
||||
uint16_t bpr = width / 8;
|
||||
size_t idx = (size_t)y * bpr + (x / 8);
|
||||
uint8_t bit = (uint8_t)(7 - (x % 8));
|
||||
buf[idx] |= (uint8_t)(1u << bit);
|
||||
}
|
||||
|
||||
esp_err_t raster_tools_convert_gray8_to_raster_384(const uint8_t *gray,
|
||||
uint16_t src_width,
|
||||
uint16_t src_height,
|
||||
bool scale_to_width,
|
||||
uint8_t threshold,
|
||||
bool invert,
|
||||
uint16_t max_height,
|
||||
uint16_t *out_width,
|
||||
uint16_t *out_height,
|
||||
uint8_t **out_raster,
|
||||
size_t *out_len,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (gray == NULL || src_width == 0 || src_height == 0 ||
|
||||
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;
|
||||
}
|
||||
|
||||
if (max_height == 0 || max_height > 3000) {
|
||||
max_height = 3000;
|
||||
}
|
||||
|
||||
uint16_t dst_width = RASTER_WIDTH;
|
||||
uint16_t dst_height = src_height;
|
||||
if (scale_to_width) {
|
||||
if (src_width == 0) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid source width");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
uint32_t scaled = ((uint32_t)src_height * RASTER_WIDTH + (src_width / 2u)) / src_width;
|
||||
if (scaled == 0) {
|
||||
scaled = 1;
|
||||
}
|
||||
if (scaled > max_height) {
|
||||
scaled = max_height;
|
||||
}
|
||||
dst_height = (uint16_t)scaled;
|
||||
} else if (src_width != RASTER_WIDTH) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "width must be 384 when scale_to_width=false");
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (dst_height == 0 || dst_height > max_height) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "invalid output height");
|
||||
}
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
size_t len = (size_t)RASTER_BYTES_PER_ROW * dst_height;
|
||||
uint8_t *raster = (uint8_t *)calloc(1, len);
|
||||
if (raster == NULL) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "no memory");
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
for (uint16_t y = 0; y < dst_height; ++y) {
|
||||
uint16_t src_y = scale_to_width
|
||||
? (uint16_t)(((uint32_t)y * src_height) / dst_height)
|
||||
: y;
|
||||
if (src_y >= src_height) {
|
||||
src_y = (uint16_t)(src_height - 1);
|
||||
}
|
||||
|
||||
for (uint16_t x = 0; x < RASTER_WIDTH; ++x) {
|
||||
uint16_t src_x = scale_to_width
|
||||
? (uint16_t)(((uint32_t)x * src_width) / RASTER_WIDTH)
|
||||
: x;
|
||||
if (src_x >= src_width) {
|
||||
src_x = (uint16_t)(src_width - 1);
|
||||
}
|
||||
|
||||
uint8_t v = gray[(size_t)src_y * src_width + src_x];
|
||||
bool black = invert ? (v > threshold) : (v < threshold);
|
||||
if (black) {
|
||||
set_black(raster, RASTER_WIDTH, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*out_width = dst_width;
|
||||
*out_height = dst_height;
|
||||
*out_raster = raster;
|
||||
*out_len = len;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static enum qrcodegen_Ecc map_qr_ecc(uint8_t ecc_level) {
|
||||
switch (ecc_level) {
|
||||
case 0:
|
||||
return qrcodegen_Ecc_LOW;
|
||||
case 1:
|
||||
return qrcodegen_Ecc_MEDIUM;
|
||||
case 2:
|
||||
return qrcodegen_Ecc_QUARTILE;
|
||||
case 3:
|
||||
default:
|
||||
return qrcodegen_Ecc_HIGH;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t raster_tools_render_qr_384(const char *text,
|
||||
uint8_t module_scale,
|
||||
uint8_t margin_modules,
|
||||
uint8_t ecc_level,
|
||||
uint16_t max_height,
|
||||
uint16_t *out_width,
|
||||
uint16_t *out_height,
|
||||
uint8_t **out_raster,
|
||||
size_t *out_len,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (text == NULL || text[0] == '\0' ||
|
||||
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;
|
||||
}
|
||||
|
||||
if (max_height == 0 || max_height > 3000) {
|
||||
max_height = 3000;
|
||||
}
|
||||
|
||||
if (margin_modules == 0) {
|
||||
margin_modules = 2;
|
||||
}
|
||||
|
||||
size_t qr_buf_len = qrcodegen_BUFFER_LEN_MAX;
|
||||
uint8_t *temp = (uint8_t *)malloc(qr_buf_len);
|
||||
uint8_t *qr = (uint8_t *)malloc(qr_buf_len);
|
||||
if (temp == NULL || qr == NULL) {
|
||||
free(temp);
|
||||
free(qr);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "no memory");
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
bool ok = qrcodegen_encodeText(text,
|
||||
temp,
|
||||
qr,
|
||||
map_qr_ecc(ecc_level),
|
||||
qrcodegen_VERSION_MIN,
|
||||
qrcodegen_VERSION_MAX,
|
||||
qrcodegen_Mask_AUTO,
|
||||
true);
|
||||
if (!ok) {
|
||||
free(temp);
|
||||
free(qr);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "qr encode failed");
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
int qr_size = qrcodegen_getSize(qr);
|
||||
int full_modules = qr_size + 2 * margin_modules;
|
||||
|
||||
if (module_scale == 0) {
|
||||
module_scale = (uint8_t)(RASTER_WIDTH / full_modules);
|
||||
if (module_scale == 0) {
|
||||
module_scale = 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t image_size = (uint32_t)full_modules * module_scale;
|
||||
if (image_size > RASTER_WIDTH || image_size > max_height) {
|
||||
free(temp);
|
||||
free(qr);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "qr too large for 384 width");
|
||||
}
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
uint16_t out_h = (uint16_t)image_size;
|
||||
size_t len = (size_t)RASTER_BYTES_PER_ROW * out_h;
|
||||
uint8_t *raster = (uint8_t *)calloc(1, len);
|
||||
if (raster == NULL) {
|
||||
free(temp);
|
||||
free(qr);
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "no memory");
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
uint16_t x_origin = (uint16_t)((RASTER_WIDTH - image_size) / 2);
|
||||
for (int my = 0; my < full_modules; ++my) {
|
||||
for (int mx = 0; mx < full_modules; ++mx) {
|
||||
int qx = mx - margin_modules;
|
||||
int qy = my - margin_modules;
|
||||
bool black = (qx >= 0 && qx < qr_size && qy >= 0 && qy < qr_size)
|
||||
? qrcodegen_getModule(qr, qx, qy)
|
||||
: false;
|
||||
if (!black) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t px0 = (uint16_t)(x_origin + mx * module_scale);
|
||||
uint16_t py0 = (uint16_t)(my * module_scale);
|
||||
for (uint8_t sy = 0; sy < module_scale; ++sy) {
|
||||
uint16_t py = (uint16_t)(py0 + sy);
|
||||
if (py >= out_h) {
|
||||
continue;
|
||||
}
|
||||
for (uint8_t sx = 0; sx < module_scale; ++sx) {
|
||||
uint16_t px = (uint16_t)(px0 + sx);
|
||||
if (px < RASTER_WIDTH) {
|
||||
set_black(raster, RASTER_WIDTH, px, py);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(temp);
|
||||
free(qr);
|
||||
|
||||
*out_width = RASTER_WIDTH;
|
||||
*out_height = out_h;
|
||||
*out_raster = raster;
|
||||
*out_len = len;
|
||||
return ESP_OK;
|
||||
}
|
||||
20
main/domain/src/system_runtime.c
Normal file
20
main/domain/src/system_runtime.c
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "system_runtime.h"
|
||||
|
||||
#include "platform_bootstrap.h"
|
||||
#include "wifi_manager.h"
|
||||
|
||||
esp_err_t system_runtime_bootstrap(void) {
|
||||
esp_err_t err = platform_bootstrap_init();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
return wifi_manager_start();
|
||||
}
|
||||
|
||||
bool system_runtime_wifi_ready(void) {
|
||||
return wifi_manager_is_ready();
|
||||
}
|
||||
|
||||
void system_runtime_get_ip(char *buf, size_t buf_len) {
|
||||
wifi_manager_get_ip(buf, buf_len);
|
||||
}
|
||||
Reference in New Issue
Block a user