mirror of
https://github.com/randybb/esphome-configs.git
synced 2026-10-14 08:35:12 +02:00
Fix crashing, display cs and fonts for cnc pendant
This commit is contained in:
1 parent
fd7a4c98b5
commit
db57574497
4 files changed
+115533
-64
No files matched your search
+28781
File diff suppressed because it is too large.
Load diff
+86576
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,148 @@
|
|||||||
|
#pragma once
|
||||||
|
// Moonraker client for mcu-cnc-pendant on its own FreeRTOS task: esp_http_client blocks
|
||||||
|
// (DNS is not bounded by timeout_ms), so the main loop only queues commands and reads
|
||||||
|
// the last polled status. An unreachable Klipper shows as "offline" instead of starving
|
||||||
|
// the loop into the task watchdog.
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "esp_http_client.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include "esphome/components/json/json_util.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace moonraker {
|
||||||
|
|
||||||
|
struct Status {
|
||||||
|
float pos[3]{};
|
||||||
|
std::string homed;
|
||||||
|
std::string state{"offline"};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
public:
|
||||||
|
void start(const char *base) {
|
||||||
|
this->base_ = base;
|
||||||
|
xTaskCreate(task, "moonraker", 6144, this, 1, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run G-code; dropped when Klipper is not keeping up.
|
||||||
|
void gcode(const std::string &script) {
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["script"] = script;
|
||||||
|
std::string body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
this->post("/printer/gcode/script", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// urgent: throw away everything queued and send this first (emergency stop)
|
||||||
|
void post(const std::string &path, const std::string &body = "", bool urgent = false) {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
if (urgent) {
|
||||||
|
this->queue_.clear();
|
||||||
|
} else if (this->queue_.size() >= MAX_QUEUE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->queue_.emplace_back(path, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status status() {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
return this->status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static constexpr size_t MAX_QUEUE = 8;
|
||||||
|
static constexpr uint32_t POLL_MS = 250;
|
||||||
|
static constexpr int TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
static void task(void *arg) {
|
||||||
|
auto *self = static_cast<Client *>(arg);
|
||||||
|
uint32_t last_poll = 0;
|
||||||
|
for (;;) {
|
||||||
|
std::pair<std::string, std::string> cmd;
|
||||||
|
bool have_cmd = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(self->mutex_);
|
||||||
|
if (!self->queue_.empty()) {
|
||||||
|
cmd = std::move(self->queue_.front());
|
||||||
|
self->queue_.pop_front();
|
||||||
|
have_cmd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (have_cmd) {
|
||||||
|
int code = self->request(cmd.first, &cmd.second, nullptr);
|
||||||
|
if (code != 200)
|
||||||
|
ESP_LOGW("moonraker", "POST %s failed (%d): %s", cmd.first.c_str(), code, cmd.second.c_str());
|
||||||
|
continue; // drain commands before the next poll
|
||||||
|
}
|
||||||
|
if (esphome::millis() - last_poll >= POLL_MS) {
|
||||||
|
last_poll = esphome::millis();
|
||||||
|
self->poll();
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void poll() {
|
||||||
|
std::string body;
|
||||||
|
Status s;
|
||||||
|
if (this->request("/printer/objects/query?gcode_move=gcode_position&toolhead=homed_axes&print_stats=state",
|
||||||
|
nullptr, &body) == 200) {
|
||||||
|
auto doc = esphome::json::parse_json(body);
|
||||||
|
JsonObject st = doc["result"]["status"];
|
||||||
|
JsonArray p = st["gcode_move"]["gcode_position"];
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
s.pos[i] = p[i];
|
||||||
|
s.homed = st["toolhead"]["homed_axes"].as<std::string>();
|
||||||
|
s.state = st["print_stats"]["state"].as<std::string>();
|
||||||
|
} else {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
s.pos[0] = this->status_.pos[0]; // keep the last position, only flag offline
|
||||||
|
s.pos[1] = this->status_.pos[1];
|
||||||
|
s.pos[2] = this->status_.pos[2];
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
this->status_ = std::move(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST when body is set, GET otherwise; returns the HTTP status or -1
|
||||||
|
int request(const std::string &path, const std::string *body, std::string *out) {
|
||||||
|
std::string url = this->base_ + path;
|
||||||
|
esp_http_client_config_t cfg{};
|
||||||
|
cfg.url = url.c_str();
|
||||||
|
cfg.timeout_ms = TIMEOUT_MS;
|
||||||
|
cfg.method = body ? HTTP_METHOD_POST : HTTP_METHOD_GET;
|
||||||
|
esp_http_client_handle_t c = esp_http_client_init(&cfg);
|
||||||
|
if (c == nullptr)
|
||||||
|
return -1;
|
||||||
|
int code = -1;
|
||||||
|
int len = body ? body->size() : 0;
|
||||||
|
if (len > 0)
|
||||||
|
esp_http_client_set_header(c, "Content-Type", "application/json");
|
||||||
|
if (esp_http_client_open(c, len) == ESP_OK && (len == 0 || esp_http_client_write(c, body->data(), len) == len)) {
|
||||||
|
esp_http_client_fetch_headers(c);
|
||||||
|
code = esp_http_client_get_status_code(c);
|
||||||
|
char buf[256];
|
||||||
|
int n;
|
||||||
|
while (out != nullptr && (n = esp_http_client_read(c, buf, sizeof(buf))) > 0)
|
||||||
|
out->append(buf, n);
|
||||||
|
}
|
||||||
|
esp_http_client_cleanup(c);
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string base_;
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::deque<std::pair<std::string, std::string>> queue_;
|
||||||
|
Status status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Client client;
|
||||||
|
|
||||||
|
} // namespace moonraker
|
||||||
+28
-64
@@ -33,6 +33,10 @@ esphome:
|
|||||||
friendly_name: ${name}
|
friendly_name: ${name}
|
||||||
area: ${area}
|
area: ${area}
|
||||||
comment: ${comment}
|
comment: ${comment}
|
||||||
|
includes:
|
||||||
|
- include/moonraker.h
|
||||||
|
on_boot:
|
||||||
|
- lambda: 'moonraker::client.start("${moonraker}");'
|
||||||
|
|
||||||
esp32:
|
esp32:
|
||||||
variant: esp32s3
|
variant: esp32s3
|
||||||
@@ -59,8 +63,8 @@ light:
|
|||||||
rgb_order: GRB
|
rgb_order: GRB
|
||||||
restore_mode: ALWAYS_OFF
|
restore_mode: ALWAYS_OFF
|
||||||
|
|
||||||
|
# only links esp_http_client for include/moonraker.h, which runs requests on its own task
|
||||||
http_request:
|
http_request:
|
||||||
timeout: 1s
|
|
||||||
|
|
||||||
globals:
|
globals:
|
||||||
- id: axis
|
- id: axis
|
||||||
@@ -68,37 +72,14 @@ globals:
|
|||||||
initial_value: "0" # 0 X, 1 Y, 2 Z
|
initial_value: "0" # 0 X, 1 Y, 2 Z
|
||||||
- id: step
|
- id: step
|
||||||
type: int
|
type: int
|
||||||
initial_value: "1" # index into steps[] in jog_interval
|
initial_value: "1" # index into steps[] in the jog interval
|
||||||
- id: wheel_sent
|
- id: wheel_sent
|
||||||
type: float
|
type: float
|
||||||
initial_value: "0"
|
initial_value: "0"
|
||||||
- id: pos
|
|
||||||
type: std::array<float, 3>
|
|
||||||
- id: homed
|
|
||||||
type: std::string
|
|
||||||
- id: state
|
|
||||||
type: std::string
|
|
||||||
initial_value: '"offline"'
|
|
||||||
|
|
||||||
script:
|
|
||||||
- id: gcode
|
|
||||||
mode: queued
|
|
||||||
max_runs: 5
|
|
||||||
parameters:
|
|
||||||
cmd: string
|
|
||||||
then:
|
|
||||||
- http_request.post:
|
|
||||||
url: ${moonraker}/printer/gcode/script
|
|
||||||
json: 'root["script"] = cmd;'
|
|
||||||
on_error:
|
|
||||||
- logger.log:
|
|
||||||
format: "gcode failed: %s"
|
|
||||||
args: [cmd.c_str()]
|
|
||||||
|
|
||||||
interval:
|
interval:
|
||||||
# handwheel: send the counts since the last jog as one relative move
|
# handwheel: send the counts since the last jog as one relative move
|
||||||
- id: jog_interval
|
- interval: 100ms
|
||||||
interval: 100ms
|
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
static const float steps[] = {0.01f, 0.1f, 1.0f};
|
static const float steps[] = {0.01f, 0.1f, 1.0f};
|
||||||
@@ -109,25 +90,7 @@ interval:
|
|||||||
// feed so the move takes ~100 ms, i.e. about one interval
|
// feed so the move takes ~100 ms, i.e. about one interval
|
||||||
// ponytail: capped feed lets fast spins queue moves that keep running after the wheel stops; drop excess counts if that bites
|
// ponytail: capped feed lets fast spins queue moves that keep running after the wheel stops; drop excess counts if that bites
|
||||||
float feed = std::min(std::max(std::fabs(dist) * 600.0f, 60.0f), ${max_feed}.0f);
|
float feed = std::min(std::max(std::fabs(dist) * 600.0f, 60.0f), ${max_feed}.0f);
|
||||||
id(gcode)->execute(str_sprintf("G91\nG1 %c%.3f F%.0f\nG90", "XYZ"[id(axis)], dist, feed));
|
moonraker::client.gcode(str_sprintf("G91\nG1 %c%.3f F%.0f\nG90", "XYZ"[id(axis)], dist, feed));
|
||||||
# DRO
|
|
||||||
- interval: 250ms
|
|
||||||
then:
|
|
||||||
- http_request.get:
|
|
||||||
url: ${moonraker}/printer/objects/query?gcode_move=gcode_position&toolhead=homed_axes&print_stats=state
|
|
||||||
capture_response: true
|
|
||||||
on_response:
|
|
||||||
- lambda: |-
|
|
||||||
json::parse_json(body, [](JsonObject root) -> bool {
|
|
||||||
JsonObject s = root["result"]["status"];
|
|
||||||
JsonArray p = s["gcode_move"]["gcode_position"];
|
|
||||||
for (int i = 0; i < 3; i++) id(pos)[i] = p[i];
|
|
||||||
id(homed) = s["toolhead"]["homed_axes"].as<std::string>();
|
|
||||||
id(state) = s["print_stats"]["state"].as<std::string>();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
on_error:
|
|
||||||
- lambda: 'id(state) = "offline";'
|
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
- platform: rotary_encoder
|
- platform: rotary_encoder
|
||||||
@@ -182,7 +145,7 @@ binary_sensor:
|
|||||||
filters:
|
filters:
|
||||||
- delayed_on_off: 20ms
|
- delayed_on_off: 20ms
|
||||||
on_press:
|
on_press:
|
||||||
- lambda: 'id(gcode)->execute(str_sprintf("G92 %c0", "XYZ"[id(axis)]));'
|
- lambda: 'moonraker::client.gcode(str_sprintf("G92 %c0", "XYZ"[id(axis)]));'
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
id: step_button
|
id: step_button
|
||||||
name: Step Button
|
name: Step Button
|
||||||
@@ -203,9 +166,7 @@ binary_sensor:
|
|||||||
filters:
|
filters:
|
||||||
- delayed_on_off: 20ms
|
- delayed_on_off: 20ms
|
||||||
on_press:
|
on_press:
|
||||||
- script.execute:
|
- lambda: 'moonraker::client.gcode("G28");'
|
||||||
id: gcode
|
|
||||||
cmd: G28
|
|
||||||
# BT2: pause / resume
|
# BT2: pause / resume
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
id: bt2
|
id: bt2
|
||||||
@@ -217,8 +178,7 @@ binary_sensor:
|
|||||||
filters:
|
filters:
|
||||||
- delayed_on_off: 20ms
|
- delayed_on_off: 20ms
|
||||||
on_press:
|
on_press:
|
||||||
- http_request.post:
|
- lambda: 'moonraker::client.post(moonraker::client.status().state == "paused" ? "/printer/print/resume" : "/printer/print/pause");'
|
||||||
url: !lambda 'return std::string("${moonraker}/printer/print/") + (id(state) == "paused" ? "resume" : "pause");'
|
|
||||||
# BT3: hold 1 s for emergency stop (Klipper needs FIRMWARE_RESTART afterwards)
|
# BT3: hold 1 s for emergency stop (Klipper needs FIRMWARE_RESTART afterwards)
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
id: bt3
|
id: bt3
|
||||||
@@ -233,15 +193,14 @@ binary_sensor:
|
|||||||
- timing:
|
- timing:
|
||||||
- ON for at least 1s
|
- ON for at least 1s
|
||||||
then:
|
then:
|
||||||
- http_request.post: ${moonraker}/printer/emergency_stop
|
- lambda: 'moonraker::client.post("/printer/emergency_stop", "", true);'
|
||||||
|
|
||||||
font:
|
font:
|
||||||
- file: "gfonts://Roboto Mono@700"
|
# X11 misc-fixed bitmap fonts (as in u8g2 / Marlin), 6 px wide: axis letter + "-123.456" fits 64 px
|
||||||
|
- file: assets/fonts/6x13.bdf
|
||||||
id: font_big
|
id: font_big
|
||||||
size: 14
|
- file: assets/fonts/6x10.bdf
|
||||||
- file: "gfonts://Roboto Mono"
|
|
||||||
id: font_small
|
id: font_small
|
||||||
size: 10
|
|
||||||
|
|
||||||
spi:
|
spi:
|
||||||
clk_pin: 13
|
clk_pin: 13
|
||||||
@@ -250,22 +209,27 @@ spi:
|
|||||||
display:
|
display:
|
||||||
- platform: st7920
|
- platform: st7920
|
||||||
id: lcd
|
id: lcd
|
||||||
cs_pin: 15
|
cs_pin:
|
||||||
|
number: 15
|
||||||
|
inverted: true # ST7920 RS/CS is active high
|
||||||
|
data_rate: 1MHz # default 200kHz takes ~220 ms per frame; drop back if the picture garbles
|
||||||
width: 128
|
width: 128
|
||||||
height: 64
|
height: 64
|
||||||
|
rotation: 90 # mounted portrait, 270 if upside down
|
||||||
update_interval: 200ms
|
update_interval: 200ms
|
||||||
lambda: |-
|
lambda: |-
|
||||||
static const char *steps[] = {"0.01", "0.1", "1"};
|
static const char *steps[] = {"0.01", "0.1", "1"};
|
||||||
it.printf(0, 0, id(font_small), "%s", id(state).c_str());
|
auto s = moonraker::client.status();
|
||||||
it.printf(127, 0, id(font_small), TextAlign::TOP_RIGHT, "H:%s", id(homed).empty() ? "-" : id(homed).c_str());
|
it.printf(0, 0, id(font_small), "%s", s.state.c_str());
|
||||||
|
it.printf(0, 10, id(font_small), "H:%s", s.homed.empty() ? "-" : s.homed.c_str());
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
int y = 12 + i * 15;
|
int y = 22 + i * 14;
|
||||||
Color c = COLOR_ON;
|
Color c = COLOR_ON;
|
||||||
if (i == id(axis)) {
|
if (i == id(axis)) {
|
||||||
it.filled_rectangle(0, y, 128, 15);
|
it.filled_rectangle(0, y, 64, 13);
|
||||||
c = COLOR_OFF;
|
c = COLOR_OFF;
|
||||||
}
|
}
|
||||||
it.printf(2, y, id(font_big), c, "%c", "XYZ"[i]);
|
it.printf(1, y + 1, id(font_big), c, "%c", "XYZ"[i]);
|
||||||
it.printf(125, y, id(font_big), c, TextAlign::TOP_RIGHT, "%9.3f", id(pos)[i]);
|
it.printf(63, y + 1, id(font_big), c, TextAlign::TOP_RIGHT, "%.3f", s.pos[i]);
|
||||||
}
|
}
|
||||||
it.printf(0, 54, id(font_small), "step %s mm", steps[id(step)]);
|
it.printf(0, 118, id(font_small), "%s mm", steps[id(step)]);
|
||||||
Reference in new issue
Block a user