From 270a92ba06a8b98d4f5627ccae0f95d1222e9fa8 Mon Sep 17 00:00:00 2001 From: JonatanRek Date: Tue, 7 Apr 2026 08:19:25 +0200 Subject: [PATCH] Refactor Marlin2 component and add new features - Updated __init__.py to include max_sd_files configuration option. - Refactored Marlin2 class to use new namespaces and improved structure. - Added support for binary sensors and select components in binary_sensor.py and select.py. - Enhanced sensor.py and text_sensor.py to include new configuration options for SD card file count and selected file. - Improved code readability and organization across multiple files. --- __init__.py | 70 ++---- automation.h | 43 ++-- binary_sensor.py | 26 +++ marlin2.cpp | 571 ++++++++++++++++++++++++----------------------- marlin2.h | 111 +++++---- select.py | 26 +++ sensor.py | 39 ++-- text_sensor.py | 16 +- 8 files changed, 492 insertions(+), 410 deletions(-) create mode 100644 binary_sensor.py create mode 100644 select.py diff --git a/__init__.py b/__init__.py index b07e26c..0728f1d 100644 --- a/__init__.py +++ b/__init__.py @@ -2,72 +2,43 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import uart from esphome.const import ( - CONF_ID, - CONF_INDEX, - CONF_SENSORS, - CONF_HUMIDITY, - CONF_MODEL, - CONF_PIN, - CONF_TEMPERATURE, - UNIT_CELSIUS, - UNIT_PERCENT, - UNIT_SECOND, - STATE_CLASS_MEASUREMENT, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_DURATION, - CONF_DATA, - CONF_VALUE + CONF_ID, + CONF_VALUE, ) -from esphome import pins, automation -from esphome.core import CORE, coroutine_with_priority +from esphome import automation +from esphome.core import coroutine_with_priority CODEOWNERS = ["@jonatanrek"] -DEPENDENCIES = ['uart'] -CONF_MARLIN2_ID = "marlin2_id" +DEPENDENCIES = ["uart"] +MULTI_CONF = False -Marlin2 = cg.esphome_ns.class_('Marlin2', cg.Component) -WriteAction = cg.esphome_ns.class_("WriteAction", automation.Action) -PrintFileAction = cg.esphome_ns.class_("PrintFileAction", automation.Action) +CONF_MARLIN2_ID = "marlin2_id" +CONF_MAX_SD_FILES = "max_sd_files" + +marlin2_ns = cg.esphome_ns.namespace("marlin2") +Marlin2 = marlin2_ns.class_("Marlin2", cg.PollingComponent, uart.UARTDevice) +WriteAction = marlin2_ns.class_("WriteAction", automation.Action) +PrintFileAction = marlin2_ns.class_("PrintFileAction", automation.Action) CONFIG_SCHEMA = cv.All( cv.Schema({ - cv.GenerateID(): cv.declare_id(Marlin2), + cv.GenerateID(): cv.declare_id(Marlin2), + cv.Optional(CONF_MAX_SD_FILES, default=20): cv.int_range(min=1, max=255), }) .extend(cv.COMPONENT_SCHEMA) .extend(uart.UART_DEVICE_SCHEMA), ) -def validate_raw_data(value): - if isinstance(value, str): - return value.encode("utf-8") - if isinstance(value, str): - return value - if isinstance(value, list): - return cv.Schema([cv.hex_uint8_t])(value) - raise cv.Invalid("data must either be a string wrapped in quotes or a list of bytes") - -@coroutine_with_priority(100.0) -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await uart.register_uart_device(var, config) - OPERATION_BASE_SCHEMA = cv.Schema({ cv.GenerateID(): cv.use_id(Marlin2), cv.Required(CONF_VALUE): cv.templatable(cv.string_strict), }) -OPERATION_BASE_SCHEMA_2 = cv.Schema({ - cv.GenerateID(): cv.use_id(Marlin2), - cv.Required(CONF_VALUE): cv.templatable(cv.string_strict), -}) - @automation.register_action( "marlin2.write", WriteAction, OPERATION_BASE_SCHEMA, ) - async def marlin2_write_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -78,11 +49,18 @@ async def marlin2_write_to_code(config, action_id, template_arg, args): @automation.register_action( "marlin2.print_file", PrintFileAction, - OPERATION_BASE_SCHEMA_2, + OPERATION_BASE_SCHEMA, ) async def marlin2_print_file_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) template_ = await cg.templatable(config[CONF_VALUE], args, cg.std_string) cg.add(var.set_value(template_)) - return var \ No newline at end of file + return var + +@coroutine_with_priority(100.0) +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + cg.add(var.set_max_sd_files(config[CONF_MAX_SD_FILES])) diff --git a/automation.h b/automation.h index 767c407..8c1a64f 100644 --- a/automation.h +++ b/automation.h @@ -4,35 +4,36 @@ #include "esphome/core/component.h" #include "marlin2.h" -namespace esphome { - static const char *TAG = "marlin2"; +namespace esphome::marlin2 { template class WriteAction : public Action { - public: - explicit WriteAction(Marlin2 *marlin2) : marlin2_(marlin2) {} - TEMPLATABLE_VALUE(std::string, value) + public: + explicit WriteAction(Marlin2 *marlin2) : marlin2_(marlin2) {} + TEMPLATABLE_VALUE(std::string, value) - void play(Ts... x) override { - this->marlin2_->write(this->value_.value(x...)); - } + void play(Ts... x) override { + this->marlin2_->write(this->value_.value(x...)); + } - protected: - Marlin2 *marlin2_; + protected: + Marlin2 *marlin2_; }; template class PrintFileAction : public Action { - public: - explicit PrintFileAction(Marlin2 *marlin2) : marlin2_(marlin2) {} - TEMPLATABLE_VALUE(std::string, value) + public: + explicit PrintFileAction(Marlin2 *marlin2) : marlin2_(marlin2) {} + TEMPLATABLE_VALUE(std::string, value) - void play(Ts... x) override { - std::string file_name = this->marlin2_->to_dos_name(this->value_.value(x...)); - ESP_LOGD(TAG, "->FILE: %s", file_name.c_str()); - this->marlin2_->write(str_sprintf("M32 P !%s#", file_name.c_str())); - } + void play(Ts... x) override { + static const char *const TAG = "marlin2"; + std::string file_name = this->marlin2_->to_dos_name(this->value_.value(x...)); + ESP_LOGD(TAG, "->FILE: %s", file_name.c_str()); + this->marlin2_->write(str_sprintf("M32 P !%s#", file_name.c_str())); + } - protected: - Marlin2 *marlin2_; + protected: + Marlin2 *marlin2_; }; -} // namespace esphome \ No newline at end of file + +} // namespace esphome::marlin2 diff --git a/binary_sensor.py b/binary_sensor.py new file mode 100644 index 0000000..da843b7 --- /dev/null +++ b/binary_sensor.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + DEVICE_CLASS_CONNECTIVITY, +) +from . import Marlin2 + +CONF_MARLIN = "marlin2" +CONF_SD_CARD_PRESENT = "sd_card_present" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MARLIN): cv.use_id(Marlin2), + cv.Optional(CONF_SD_CARD_PRESENT): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_CONNECTIVITY, + ), + } +).extend(cv.polling_component_schema("15s")) + +async def to_code(config): + server = await cg.get_variable(config[CONF_MARLIN]) + + if CONF_SD_CARD_PRESENT in config: + bs = await binary_sensor.new_binary_sensor(config[CONF_SD_CARD_PRESENT]) + cg.add(server.add_binary_sensor(CONF_SD_CARD_PRESENT, bs)) diff --git a/marlin2.cpp b/marlin2.cpp index 88f097d..e102fc3 100644 --- a/marlin2.cpp +++ b/marlin2.cpp @@ -2,416 +2,433 @@ #include "esphome/core/log.h" #include -namespace esphome { +namespace esphome::marlin2 { static const char *TAG = "marlin2"; - + #ifdef USE_SENSOR - void Marlin2::add_sensor(const std::string& sName, sensor::Sensor *sens) { - sensors.push_back({sName, sens}); + void Marlin2::add_sensor(const std::string &name, sensor::Sensor *sens) { + sensors_.push_back({name, sens}); } - - sensor::Sensor* Marlin2::find_sensor(std::string key) { - for (const auto& pair : sensors) { - if (key == std::string(pair.first)) { // Convert char* to std::string for comparison + + sensor::Sensor *Marlin2::find_sensor(const std::string &key) { + for (const auto &pair : sensors_) { + if (key == pair.first) { return pair.second; } } - return nullptr; // Return nullptr if no match is found + return nullptr; } #endif - + #ifdef USE_TEXT_SENSOR - void Marlin2::add_text_sensor(const std::string& sName, text_sensor::TextSensor *sens) { - text_sensors.push_back({sName, sens}); + void Marlin2::add_text_sensor(const std::string &name, text_sensor::TextSensor *sens) { + text_sensors_.push_back({name, sens}); } - - text_sensor::TextSensor* Marlin2::find_text_sensor(std::string key) { - for (const auto& pair : text_sensors) { - if (key == std::string(pair.first)) { // Convert char* to std::string for comparison + + text_sensor::TextSensor *Marlin2::find_text_sensor(const std::string &key) { + for (const auto &pair : text_sensors_) { + if (key == pair.first) { return pair.second; } } - return nullptr; // Return nullptr if no match is found + return nullptr; } #endif - + + #ifdef USE_SELECT + void Marlin2::add_select(const std::string &name, select::Select *sel) { + selects_.push_back({name, sel}); + } + + select::Select *Marlin2::find_select(const std::string &key) { + for (const auto &pair : selects_) { + if (key == pair.first) { + return pair.second; + } + } + return nullptr; + } + #endif + + #ifdef USE_BINARY_SENSOR + void Marlin2::add_binary_sensor(const std::string &name, binary_sensor::BinarySensor *bs) { + binary_sensors_.push_back({name, bs}); + } + + binary_sensor::BinarySensor *Marlin2::find_binary_sensor(const std::string &key) { + for (const auto &pair : binary_sensors_) { + if (key == pair.first) { + return pair.second; + } + } + return nullptr; + } + #endif + void Marlin2::setup() { - MarlinOutput.reserve(256); - MarlinOutput = ""; - - MarlinResponseOutput.reserve(256); - MarlinResponseOutput = ""; - - MarlinTime.reserve(32); - PrinterState.reserve(32); - - //ESP_LOGD(TAG, "M155 S10"); - + marlin_output_.reserve(256); + marlin_response_output_.reserve(256); + marlin_time_.reserve(32); + printer_state_.reserve(32); + write_str("\r\n\r\nM155 S10\r\n"); write_str("\r\n\r\nM117 ESP Home Connected!\r\n"); flush(); - + set_printer_state("IDLE"); } - + + void Marlin2::dump_config() { + ESP_LOGCONFIG(TAG, "Marlin2:"); + this->check_uart_settings(115200); + LOG_UPDATE_INTERVAL(this); + } + void Marlin2::write(std::string gcode) { ESP_LOGD(TAG, "->GCODE: %s", gcode.c_str()); write_str((std::string("\r\n\r\n") + gcode + std::string("\r\n")).c_str()); flush(); } - + void Marlin2::update() { while (available()) { char c = read(); - if( c == '\n' || c == '\r' ) { - //ESP_LOGD(TAG, "#%s#",MarlinOutput.c_str()); + if (c == '\n' || c == '\r') { process_line(); } else { - MarlinOutput += c; + marlin_output_ += c; } } - - if(millis() - millisProgress > 15000 && print_progress != 100) { - millisProgress = millis(); - - //ESP_LOGD(TAG, "M27"); - //ESP_LOGD(TAG, "M31"); - + + if (millis() - millis_progress_ > 15000 && print_progress_ != 100) { + millis_progress_ = millis(); write_str("M27\r\nM31\r\n"); } } - - void Marlin2::process_line() { - if(MarlinOutput.size() < 3) { - MarlinOutput=""; + + void Marlin2::process_line() { + if (marlin_output_.size() < 3) { + marlin_output_ = ""; return; } - - if(MarlinOutput.compare("ok") == 0 || MarlinOutput.compare(" ok") == 0) { - listingFile = false; - MarlinOutput=""; + + if (marlin_output_.compare("ok") == 0 || marlin_output_.compare(" ok") == 0) { + listing_file_ = false; + marlin_output_ = ""; return; } - - - ESP_LOGD(TAG, "DEBUG>#%s#",MarlinOutput.c_str()); - - if(!listingFile && MarlinOutput.compare("Begin file list") == 0) { + + ESP_LOGD(TAG, "DEBUG>#%s#", marlin_output_.c_str()); + + if (!listing_file_ && marlin_output_.compare("Begin file list") == 0) { ESP_LOGD(TAG, "Listing of files started!"); - listingFile = true; - //reset string for next line - MarlinOutput=""; + listing_file_ = true; + file_table_.clear(); + marlin_output_ = ""; return; } - - if(listingFile && MarlinOutput.compare("End file list") == 0) { + + if (listing_file_ && marlin_output_.compare("End file list") == 0) { ESP_LOGD(TAG, "Listing of files stopped!"); - listingFile = false; - //reset string for next line - - #ifdef USE_TEXT_SENSOR - std::string sd_files; - - for (const auto& [key, value] : file_table_) { - if (!sd_files.empty()) sd_files += "|"; - sd_files += key; - } - - if (find_text_sensor("sd_card_files") != nullptr){ - ESP_LOGD(TAG, "SD Files: %s", sd_files.c_str()); - find_text_sensor("sd_card_files")->publish_state(sd_files.c_str()); + listing_file_ = false; + + #ifdef USE_BINARY_SENSOR + if (find_binary_sensor("sd_card_present") != nullptr) { + find_binary_sensor("sd_card_present")->publish_state(true); } #endif - - MarlinOutput=""; + + publish_sd_files(); + + #ifdef USE_SENSOR + if (find_sensor("sd_card_file_count") != nullptr) { + find_sensor("sd_card_file_count")->publish_state(static_cast(file_table_.size())); + } + #endif + + marlin_output_ = ""; return; } - - if(listingFile && MarlinOutput.find(".GCO ") > 1) { - int first_space = MarlinOutput.find(' '); - int second_space = MarlinOutput.find(' ', first_space + 1); - - if(first_space > 0) { - std::string short_name = MarlinOutput.substr(0, first_space); - - if (short_name.find("/") != std::string::npos){ // Omit subdirectories - ESP_LOGD(TAG, "peskočeno kvůli vnořen %s",short_name.c_str()); - MarlinOutput=""; + + if (listing_file_ && marlin_output_.find(".GCO ") > 1) { + size_t first_space = marlin_output_.find(' '); + size_t second_space = marlin_output_.find(' ', first_space + 1); + + if (first_space != std::string::npos) { + std::string short_name = marlin_output_.substr(0, first_space); + + if (short_name.find("/") != std::string::npos) { + ESP_LOGD(TAG, "Skipping subdirectory: %s", short_name.c_str()); + marlin_output_ = ""; return; } - + std::string long_name = short_name; - - if (second_space > first_space){ - long_name = MarlinOutput.substr(second_space + 1); + + if (second_space != std::string::npos && second_space > first_space) { + long_name = marlin_output_.substr(second_space + 1); } - + file_table_[long_name] = short_name; } - - //reset string for next line - MarlinOutput=""; + + marlin_output_ = ""; return; } - - //Parse periodic Temperature read out message - if( - MarlinOutput.find(" T:") == 0 || - MarlinOutput.find("T:") == 0 || - MarlinOutput.find("ok T:") == 0 || - MarlinOutput.find(" ok T:") == 0 + + // Parse periodic temperature read out message + if ( + marlin_output_.find(" T:") == 0 || + marlin_output_.find("T:") == 0 || + marlin_output_.find("ok T:") == 0 || + marlin_output_.find(" ok T:") == 0 ) { float ext_temperature, ext_set_temperature, bed_temperature, bed_set_temperature; - if (process_temp_msg(&ext_temperature, &ext_set_temperature, &bed_temperature, &bed_set_temperature) != 0) { + if (process_temp_msg(&ext_temperature, &ext_set_temperature, &bed_temperature, &bed_set_temperature) != 0) { #ifdef USE_SENSOR if (find_sensor("bed_temperature") != nullptr) - find_sensor("bed_temperature")->publish_state(bed_temperature); - + find_sensor("bed_temperature")->publish_state(bed_temperature); + if (find_sensor("bed_set_temperature") != nullptr) - find_sensor("bed_set_temperature")->publish_state(bed_set_temperature); - + find_sensor("bed_set_temperature")->publish_state(bed_set_temperature); + if (find_sensor("ext_temperature") != nullptr) - find_sensor("ext_temperature")->publish_state(ext_temperature); - + find_sensor("ext_temperature")->publish_state(ext_temperature); + if (find_sensor("ext_set_temperature") != nullptr) - find_sensor("ext_set_temperature")->publish_state(ext_set_temperature); + find_sensor("ext_set_temperature")->publish_state(ext_set_temperature); #endif #ifdef USE_TEXT_SENSOR - if(bed_set_temperature==0.0 && ext_set_temperature==0.0) { - if(ext_temperature < 32.0 && bed_temperature < 32.0){ //TODO define constants for these + if (bed_set_temperature == 0.0 && ext_set_temperature == 0.0) { + if (ext_temperature < 32.0 && bed_temperature < 32.0) { set_printer_state("IDLE"); - } - else if(ext_temperature < 150.0 && bed_temperature < 55.0){ + } else if (ext_temperature < 150.0 && bed_temperature < 55.0) { set_printer_state("COOLING"); } } - if(print_progress == 0.0 && (bed_set_temperature!=0.0 || ext_set_temperature!=0.0)) { - //print_time_offset = print_time save print time ofset to deduct from total value send to hass + if (print_progress_ == 0.0 && (bed_set_temperature != 0.0 || ext_set_temperature != 0.0)) { set_printer_state("PREHEATING"); } #endif - - //ESP_LOGD(TAG, "Bed Temperature=%.1f°C Ext Temperature=%.1f°C ", bed_temperature, ext_temperature); } - - //reset string for next line - MarlinOutput=""; + + marlin_output_ = ""; return; } - - //Parse Progress of the print - if(MarlinOutput.find("SD printing byte") == 0 ) { - print_progress = process_progress_msg(); + + // Parse progress of the print + if (marlin_output_.find("SD printing byte") == 0) { + print_progress_ = process_progress_msg(); #ifdef USE_SENSOR if (find_sensor("print_progress") != nullptr) - find_sensor("print_progress")->publish_state(print_progress); - - //ESP_LOGD(TAG, "progress=%.1f", print_progress); + find_sensor("print_progress")->publish_state(print_progress_); #endif - + set_printer_state("PRINTING"); - //reset string for next line - MarlinOutput=""; + marlin_output_ = ""; return; } - - //Parse Printitme - if(MarlinOutput.find("echo:Print time: ") == 0) { - double current=0; - double remaining=0; - if (process_print_time_msg(¤t, &remaining, print_progress) != 0) { - #ifdef USE_SENSOR + + // Parse print time + if (marlin_output_.find("echo:Print time: ") == 0) { + double current = 0; + double remaining = 0; + if (process_print_time_msg(¤t, &remaining, print_progress_) != 0) { + #ifdef USE_SENSOR if (find_sensor("print_time") != nullptr) - find_sensor("print_time")->publish_state(current); - + find_sensor("print_time")->publish_state(current); + if (find_sensor("print_time_remaining") != nullptr) - find_sensor("print_time_remaining")->publish_state(remaining); + find_sensor("print_time_remaining")->publish_state(remaining); #endif - - //ESP_LOGD(TAG, "time=%f remaining=%f", current, remaining); } - - //reset string for next line - MarlinOutput=""; + + marlin_output_ = ""; return; } - - //File opened: salma_~2.gco Size: 12279971 - if(MarlinOutput.find("File opened: ") == 0) { - size_t first_space = MarlinOutput.find("File opened: "); - size_t second_space = MarlinOutput.find(" Size: "); - - if (first_space != std::string::npos && second_space != std::string::npos) { - size_t start = first_space + std::strlen("File opened: "); - size_t len = second_space - start; - ESP_LOGD(TAG, "position"); - std::string filename = from_dos_name(MarlinOutput.substr(start, len)); - ESP_LOGD("Soubor: %s\n", filename.c_str()); - + + // File opened: SALMA_~2.GCO Size: 12279971 + if (marlin_output_.find("File opened: ") == 0) { + size_t start_pos = std::strlen("File opened: "); + size_t size_pos = marlin_output_.find(" Size: "); + + if (size_pos != std::string::npos) { + std::string filename = from_dos_name(marlin_output_.substr(start_pos, size_pos - start_pos)); + ESP_LOGD(TAG, "Soubor: %s", filename.c_str()); + #ifdef USE_TEXT_SENSOR - if (find_text_sensor("sd_card_file_selected") != nullptr){ + if (find_text_sensor("sd_card_file_selected") != nullptr) { find_text_sensor("sd_card_file_selected")->publish_state(filename.c_str()); } #endif } - - //reset string for next line - MarlinOutput=""; + + marlin_output_ = ""; return; } - - //Print From SD Card Started - if(MarlinOutput.compare("File selected") == 0) { + + // Print from SD card started + if (marlin_output_.compare("File selected") == 0) { set_printer_state("PRINTING"); - - //reset string for next line - MarlinOutput=""; + marlin_output_ = ""; return; } - - //Print Finished - if(MarlinOutput.compare("Done printing") == 0) { - print_progress = 100; - #ifdef USE_SENSOR + + // Print finished + if (marlin_output_.compare("Done printing") == 0) { + print_progress_ = 100; + #ifdef USE_SENSOR if (find_sensor("print_progress") != nullptr) - find_sensor("print_progress")->publish_state(print_progress); - + find_sensor("print_progress")->publish_state(print_progress_); + if (find_sensor("print_time_remaining") != nullptr) - find_sensor("print_time_remaining")->publish_state(0); + find_sensor("print_time_remaining")->publish_state(0); #endif - #ifdef USE_TEXT_SENSOR + #ifdef USE_TEXT_SENSOR set_printer_state("FINISHED"); - #endif - - //reset string for next line - MarlinOutput=""; + #endif + + marlin_output_ = ""; return; } - - // //Print Paused - // if(MarlinOutput.compare("Printer halted") == 0) { - // set_printer_state("PAUSED"); - - // //reset string for next line - // MarlinOutput=""; - // return; - // } - - // //Print Stoped - if(MarlinOutput.compare("Print Aborted") == 0) { + + // Print aborted + if (marlin_output_.compare("Print Aborted") == 0) { set_printer_state("STOPPED"); - - //reset string for next line - MarlinOutput=""; + marlin_output_ = ""; return; - } - - ESP_LOGD(TAG, ">#%s#",MarlinOutput.c_str()); - MarlinOutput=""; - return; + } + + ESP_LOGD(TAG, ">#%s#", marlin_output_.c_str()); + marlin_output_ = ""; } - - int Marlin2::process_temp_msg(float* ext_temperature, float* ext_set_temperature, float* bed_temperature, float* bed_set_temperature) { + + void Marlin2::publish_sd_files() { + #if defined(USE_TEXT_SENSOR) || defined(USE_SELECT) + std::string chunk; + uint8_t count = 0; + + #ifdef USE_SELECT + std::vector file_names; + #endif + + for (const auto &[key, value] : file_table_) { + if (count >= max_sd_files_) { + ESP_LOGW(TAG, "SD file list truncated at %u files (max_sd_files=%u, total=%u)", + max_sd_files_, max_sd_files_, (unsigned) file_table_.size()); + break; + } + + #ifdef USE_SELECT + file_names.push_back(key); + #endif + + if (!chunk.empty()) chunk += "|"; + chunk += key; + count++; + } + + #ifdef USE_TEXT_SENSOR + if (find_text_sensor("sd_card_files") != nullptr) { + find_text_sensor("sd_card_files")->publish_state(chunk); + } + #endif + + #ifdef USE_SELECT + if (find_select("sd_card_file_select") != nullptr) { + find_select("sd_card_file_select")->traits.set_options(file_names); + } + #endif + #endif + } + + int Marlin2::process_temp_msg(float *ext_temperature, float *ext_set_temperature, float *bed_temperature, float *bed_set_temperature) { float dc; - - while(MarlinOutput.find(" ") != std::string::npos) - MarlinOutput.erase(MarlinOutput.find(' '), 1); - - while(MarlinOutput.find("ok") != std::string::npos) - MarlinOutput.erase(MarlinOutput.find("ok"), 2); - - if(sscanf(MarlinOutput.c_str() ,"T:%f/%fB:%f/%f", ext_temperature, ext_set_temperature, bed_temperature, bed_set_temperature) == 4 ) - return 1; - - if(sscanf(MarlinOutput.c_str() ,"T:%f/%f(%f)B:%f/%f(%f)", ext_temperature, ext_set_temperature, &dc, bed_temperature, bed_set_temperature, &dc) == 6 ) - return 2; - - if(sscanf(MarlinOutput.c_str() ,"T:%f/%fT0:%f/%fT1:%f/%fB:%f/%f", ext_temperature, ext_set_temperature, &dc, &dc, &dc, &dc, bed_temperature, bed_set_temperature) == 8 ) - return 3; - - if(sscanf(MarlinOutput.c_str() ,"T0:%f/%fT1:%f/%fB:%f/%f", ext_temperature, ext_set_temperature, &dc, &dc, bed_temperature, bed_set_temperature) == 6 ) - return 4; - + + while (marlin_output_.find(" ") != std::string::npos) + marlin_output_.erase(marlin_output_.find(' '), 1); + + while (marlin_output_.find("ok") != std::string::npos) + marlin_output_.erase(marlin_output_.find("ok"), 2); + + if (sscanf(marlin_output_.c_str(), "T:%f/%fB:%f/%f", ext_temperature, ext_set_temperature, bed_temperature, bed_set_temperature) == 4) + return 1; + + if (sscanf(marlin_output_.c_str(), "T:%f/%f(%f)B:%f/%f(%f)", ext_temperature, ext_set_temperature, &dc, bed_temperature, bed_set_temperature, &dc) == 6) + return 2; + + if (sscanf(marlin_output_.c_str(), "T:%f/%fT0:%f/%fT1:%f/%fB:%f/%f", ext_temperature, ext_set_temperature, &dc, &dc, &dc, &dc, bed_temperature, bed_set_temperature) == 8) + return 3; + + if (sscanf(marlin_output_.c_str(), "T0:%f/%fT1:%f/%fB:%f/%f", ext_temperature, ext_set_temperature, &dc, &dc, bed_temperature, bed_set_temperature) == 6) + return 4; + return 0; } - - float Marlin2::process_progress_msg(){ - float current = std::stoi(MarlinOutput.substr(17)); - float total = std::stoi(MarlinOutput.substr(MarlinOutput.find('/')+1)); - - if (total==0) { - return 0.0; - } - - return round(((float) current * 100.0) / (float) total); + + float Marlin2::process_progress_msg() { + size_t slash_pos = marlin_output_.find('/'); + if (slash_pos == std::string::npos || slash_pos + 1 >= marlin_output_.size()) + return 0.0f; + + float current = std::stof(marlin_output_.substr(17)); + float total = std::stof(marlin_output_.substr(slash_pos + 1)); + + if (total < 1.0f) + return 0.0f; + + return roundf((current * 100.0f) / total); } - - int Marlin2::process_print_time_msg(double* current, double* remaining, float progress){ - MarlinTime = MarlinOutput.substr(16); - float d = 0, h = 0, m = 0, s = 0; - - //ESP_LOGD(TAG,MarlinTime.c_str()); - - if (sscanf(MarlinTime.c_str() ,"%fd %fh %fm %fs", &d, &h, &m, &s)!=4) { - d=0; - if (sscanf(MarlinTime.c_str() ,"%fh %fm %fs", &h, &m, &s)!=3) { - d=0; h=0; - if (sscanf(MarlinTime.c_str() ,"%fm %fs", &m, &s)!=2) { - d=0; h=0; m=0; - if (sscanf(MarlinTime.c_str() ,"%fs", &s)!=1) { + + int Marlin2::process_print_time_msg(double *current, double *remaining, float progress) { + marlin_time_ = marlin_output_.substr(16); + float d = 0, h = 0, m = 0, s = 0; + + if (sscanf(marlin_time_.c_str(), "%fd %fh %fm %fs", &d, &h, &m, &s) != 4) { + d = 0; + if (sscanf(marlin_time_.c_str(), "%fh %fm %fs", &h, &m, &s) != 3) { + d = 0; h = 0; + if (sscanf(marlin_time_.c_str(), "%fm %fs", &m, &s) != 2) { + d = 0; h = 0; m = 0; + if (sscanf(marlin_time_.c_str(), "%fs", &s) != 1) { return 0; } } } } - - *current = round(((d)*24*60*60) + ((h)*60*60) + ((m)*60) + (s)); - - if(progress != 0.0 && progress != 100.0) { + + *current = round(((d) * 24 * 60 * 60) + ((h) * 60 * 60) + ((m) * 60) + (s)); + + if (progress != 0.0 && progress != 100.0) { *remaining = (((100 * *current) / round(progress)) - *current); - } - + } + return 1; } - - void Marlin2::set_printer_state(std::string status){ - #ifdef USE_TEXT_SENSOR - // if (!PrinterState.compare(status)) - // return; - - if (find_text_sensor("printer_state") != nullptr){ + + void Marlin2::set_printer_state(std::string status) { + #ifdef USE_TEXT_SENSOR + if (find_text_sensor("printer_state") != nullptr) { find_text_sensor("printer_state")->publish_state(status); } - - // ESP_LOGD(TAG, "Printer Status %s", status.c_str()); - // PrinterState = status; #endif } - + std::string Marlin2::to_dos_name(std::string filename) { auto it = file_table_.find(filename); if (it != file_table_.end()) { - return file_table_[filename]; + return it->second; } - return filename; } - std::string Marlin2::from_dos_name(std::string dos_filename ) { - for (const auto& [key, value] : file_table_) { + std::string Marlin2::from_dos_name(std::string dos_filename) { + for (const auto &[key, value] : file_table_) { if (value == dos_filename) { return key; } } - return dos_filename; } - std::string to_lower(std::string s) { - return s; - // String arduinoStr(s.c_str()); // převod na Arduino String - // arduinoStr.toLowerCase(); // změní obsah přímo - // return std::string(arduinoStr.c_str()); // zpět na std::string - } - -} // namespace esphome \ No newline at end of file +} // namespace esphome::marlin2 diff --git a/marlin2.h b/marlin2.h index 73ca6f7..bb676b6 100644 --- a/marlin2.h +++ b/marlin2.h @@ -10,56 +10,81 @@ #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" #endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif -namespace esphome { +namespace esphome::marlin2 { class Marlin2 : public PollingComponent, public uart::UARTDevice { - public: - Marlin2() = default; + public: + Marlin2() = default; - #ifdef USE_SENSOR - void add_sensor(const std::string& sName, sensor::Sensor *sens); - sensor::Sensor* find_sensor(std::string key); - #endif - #ifdef USE_TEXT_SENSOR - void add_text_sensor(const std::string& sName, text_sensor::TextSensor *tSens); - text_sensor::TextSensor* find_text_sensor(std::string key); - #endif - void write(std::string status); - std::string to_dos_name(std::string filename); - std::string from_dos_name(std::string dos_filename); - std::string to_lower(std::string s); - - float get_setup_priority() const override { return setup_priority::LATE; } - void setup() override; - void update() override; + #ifdef USE_SENSOR + void add_sensor(const std::string &name, sensor::Sensor *sens); + sensor::Sensor *find_sensor(const std::string &key); + #endif + #ifdef USE_TEXT_SENSOR + void add_text_sensor(const std::string &name, text_sensor::TextSensor *tSens); + text_sensor::TextSensor *find_text_sensor(const std::string &key); + #endif + #ifdef USE_SELECT + void add_select(const std::string &name, select::Select *sel); + select::Select *find_select(const std::string &key); + #endif + #ifdef USE_BINARY_SENSOR + void add_binary_sensor(const std::string &name, binary_sensor::BinarySensor *bs); + binary_sensor::BinarySensor *find_binary_sensor(const std::string &key); + #endif - protected: - std::string MarlinOutput; - std::string MarlinResponseOutput; - std::string MarlinTime; - std::string PrinterState; + void set_max_sd_files(uint8_t n) { max_sd_files_ = n; } - float print_progress = 0; - double print_time_offset = 0; - - #ifdef USE_SENSOR - std::vector> sensors; - #endif - #ifdef USE_TEXT_SENSOR - std::vector> text_sensors; - #endif + void write(std::string gcode); + std::string to_dos_name(std::string filename); + std::string from_dos_name(std::string dos_filename); - void process_line(); - void set_printer_state(std::string status); - int process_temp_msg(float* ext_temperature, float* ext_set_temperature, float* bed_temperature, float* bed_set_temperature); - float process_progress_msg(); - int process_print_time_msg(double* current, double* remaining, float progress); + float get_setup_priority() const override { return setup_priority::LATE; } + void setup() override; + void update() override; + void dump_config() override; - private: - unsigned long millisProgress=0; - std::unordered_map file_table_; - bool listingFile = false; + protected: + std::string marlin_output_; + std::string marlin_response_output_; + std::string marlin_time_; + std::string printer_state_; + + float print_progress_ = 0; + double print_time_offset_ = 0; + uint8_t max_sd_files_ = 20; + + #ifdef USE_SENSOR + std::vector> sensors_; + #endif + #ifdef USE_TEXT_SENSOR + std::vector> text_sensors_; + #endif + #ifdef USE_SELECT + std::vector> selects_; + #endif + #ifdef USE_BINARY_SENSOR + std::vector> binary_sensors_; + #endif + + void process_line(); + void set_printer_state(std::string status); + void publish_sd_files(); + int process_temp_msg(float *ext_temperature, float *ext_set_temperature, float *bed_temperature, float *bed_set_temperature); + float process_progress_msg(); + int process_print_time_msg(double *current, double *remaining, float progress); + + private: + unsigned long millis_progress_ = 0; + std::unordered_map file_table_; + bool listing_file_ = false; }; -} // namespace esphome \ No newline at end of file +} // namespace esphome::marlin2 diff --git a/select.py b/select.py new file mode 100644 index 0000000..6e292e7 --- /dev/null +++ b/select.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import select +from esphome.const import ( + ENTITY_CATEGORY_NONE, +) +from . import Marlin2 + +CONF_MARLIN = "marlin2" +CONF_SD_CARD_FILE_SELECT = "sd_card_file_select" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MARLIN): cv.use_id(Marlin2), + cv.Optional(CONF_SD_CARD_FILE_SELECT): select.select_schema( + entity_category=ENTITY_CATEGORY_NONE, + ), + } +).extend(cv.polling_component_schema("15s")) + +async def to_code(config): + server = await cg.get_variable(config[CONF_MARLIN]) + + if CONF_SD_CARD_FILE_SELECT in config: + sel = await select.new_select(config[CONF_SD_CARD_FILE_SELECT], options=[]) + cg.add(server.add_select(CONF_SD_CARD_FILE_SELECT, sel)) diff --git a/sensor.py b/sensor.py index 4dcf265..30ae8cd 100644 --- a/sensor.py +++ b/sensor.py @@ -1,21 +1,12 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation -from esphome.components import uart from esphome.components import sensor -# from esphome.components import text_sensor from esphome.const import ( - CONF_ID, - CONF_INDEX, - CONF_SENSORS, - CONF_HUMIDITY, - CONF_MODEL, - CONF_PIN, - CONF_TEMPERATURE, - UNIT_CELSIUS, - UNIT_PERCENT, - UNIT_SECOND, - STATE_CLASS_MEASUREMENT, + CONF_ID, + UNIT_CELSIUS, + UNIT_PERCENT, + UNIT_SECOND, + STATE_CLASS_MEASUREMENT, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_DURATION, ) @@ -30,9 +21,10 @@ CONF_EXT_SET_TEMPERATURE = "ext_set_temperature" CONF_PRINT_PROGRESS = "print_progress" CONF_PRINT_TIME = "print_time" CONF_PRINT_TIME_REMAINING = "print_time_remaining" +CONF_SD_CARD_FILE_COUNT = "sd_card_file_count" CONF_MARLIN = "marlin2" -CONFIG_SCHEMA = cv.Schema( +CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_MARLIN): cv.use_id(Marlin2), cv.Optional(CONF_BED_TEMPERATURE): sensor.sensor_schema( @@ -76,13 +68,26 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_DURATION, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_SD_CARD_FILE_COUNT): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), } ).extend(cv.polling_component_schema("15s")) async def to_code(config): server = await cg.get_variable(config[CONF_MARLIN]) - for sName in [CONF_BED_TEMPERATURE, CONF_BED_SET_TEMPERATURE, CONF_EXT_TEMPERATURE, CONF_EXT_SET_TEMPERATURE, CONF_PRINT_PROGRESS, CONF_PRINT_TIME, CONF_PRINT_TIME_REMAINING]: + for sName in [ + CONF_BED_TEMPERATURE, + CONF_BED_SET_TEMPERATURE, + CONF_EXT_TEMPERATURE, + CONF_EXT_SET_TEMPERATURE, + CONF_PRINT_PROGRESS, + CONF_PRINT_TIME, + CONF_PRINT_TIME_REMAINING, + CONF_SD_CARD_FILE_COUNT, + ]: if sName in config: sens = await sensor.new_sensor(config[sName]) - cg.add(server.add_sensor(sName,sens)) \ No newline at end of file + cg.add(server.add_sensor(sName, sens)) diff --git a/text_sensor.py b/text_sensor.py index cd0d22f..81dfe8d 100644 --- a/text_sensor.py +++ b/text_sensor.py @@ -7,16 +7,20 @@ from esphome.const import ( from . import Marlin2 CONF_MARLIN = "marlin2" -CONFIG_SCHEMA = cv.Schema( +CONF_PRINTER_STATE = "printer_state" +CONF_SD_CARD_FILES = "sd_card_files" +CONF_SD_CARD_FILE_SELECTED = "sd_card_file_selected" + +CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_MARLIN): cv.use_id(Marlin2), - cv.Optional("printer_state"): text_sensor.text_sensor_schema( + cv.Optional(CONF_PRINTER_STATE): text_sensor.text_sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), - cv.Optional("sd_card_files"): text_sensor.text_sensor_schema( + cv.Optional(CONF_SD_CARD_FILES): text_sensor.text_sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), - cv.Optional("sd_card_file_selected"): text_sensor.text_sensor_schema( + cv.Optional(CONF_SD_CARD_FILE_SELECTED): text_sensor.text_sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } @@ -25,7 +29,7 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): server = await cg.get_variable(config[CONF_MARLIN]) - for sName in ["printer_state", "sd_card_files", "sd_card_file_selected"]: + for sName in [CONF_PRINTER_STATE, CONF_SD_CARD_FILES, CONF_SD_CARD_FILE_SELECTED]: if sName in config: sens = await text_sensor.new_text_sensor(config[sName]) - cg.add(server.add_text_sensor(sName,sens)) \ No newline at end of file + cg.add(server.add_text_sensor(sName, sens))