diff --git a/firmware/.gitignore b/firmware/.gitignore index 99358b3..b91ab12 100644 --- a/firmware/.gitignore +++ b/firmware/.gitignore @@ -1,5 +1,7 @@ /.esphome/ /secrets.yaml esphome_env/* +.gdb_history +.DS_Store *.pyc \ No newline at end of file diff --git a/firmware/Makefile b/firmware/Makefile index b45fa23..447be03 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -17,6 +17,8 @@ clean: esphome clean lolin_s2_mini.yaml esphome clean lolin_s3_mini.yaml esphome clean esp32-s3-devkitc-1.yaml + esphome clean esp32_s3_super_mini.yaml + esphome clean rpipicow.yaml rm -rf .esphome/idedata/* rm -rf ~/.platformio/penv # build-esp32s2-mini: @@ -69,6 +71,12 @@ lolin_s2_mini: lolin_s3_mini: esphome \ run lolin_s3_mini.yaml --device $(USB_ADDRESS) +rpipicow: + esphome \ + run rpipicow.yaml --device $(USB_ADDRESS) devkit: esphome \ - run esp32-s3-devkitc-1.yaml --device $(USB_ADDRESS) \ No newline at end of file + run esp32-s3-devkitc-1.yaml --device $(USB_ADDRESS) +s3_super_mini: + esphome \ + run esp32_s3_super_mini.yaml --device $(USB_ADDRESS) diff --git a/firmware/bambu.h b/firmware/bambu.h index 58041ad..1859d27 100644 --- a/firmware/bambu.h +++ b/firmware/bambu.h @@ -123,7 +123,7 @@ namespace bambulabs } } - if (doc_in["color_hex"].as().length() != 6 && doc_in["color_hex"].as().length() != 8) { + if (doc_in["color_hex"].as().length() > 0 && doc_in["color_hex"].as().length() != 6 && doc_in["color_hex"].as().length() != 8) { ESP_LOGE("bambu", "Invalid color_hex length (expected 6 or 8 characters)"); return {}; } @@ -133,7 +133,7 @@ namespace bambulabs print["command"] = "ams_filament_setting"; print["ams_id"] = ams_id; print["tray_id"] = ams_tray; - if (doc_in["color_hex"].as().length() == 6) { + if (doc_in["color_hex"].as().length() > 0 && doc_in["color_hex"].as().length() == 6) { print["tray_color"] = doc_in["color_hex"].as() + "FF"; } else{ diff --git a/firmware/common.yaml b/firmware/common.yaml index abf9f48..ca118b7 100644 --- a/firmware/common.yaml +++ b/firmware/common.yaml @@ -1,6 +1,18 @@ --- substitutions: name: openspool + +globals: + - id: global_lan_access_code + type: std::string + restore_value: yes + - id: global_bambu_ip + type: std::string + restore_value: yes + - id: global_bambu_serial + type: std::string + restore_value: yes + esphome: name: ${name} name_add_mac_suffix: true @@ -13,6 +25,7 @@ esphome: build_flags: - -std=gnu++14 - -DMBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\" + # - -DDISABLE_WATCHDOG=1 on_boot: then: #TODO: breahting blue studders a little bit @@ -36,15 +49,27 @@ esphome: id: neopixel_light effect: none brightness: 50% - # - delay: 100ms - # - light.addressable_set: - # id: neopixel_light - # color_brightness: 50% - # range_from: 0 - # range_to: ${led_count} - # red: 50% - # green: 50% - # blue: 50% + # # - delay: 100ms + # - light.addressable_set: + # id: neopixel_light + # color_brightness: 50% + # range_from: 0 + # range_to: ${led_count} + # red: 50% + # green: 50% + # blue: 50% + - text.set: + id: bambu_lan_access_code + value: !lambda |- + return id(global_lan_access_code); + - text.set: + id: bambu_ip_address + value: !lambda |- + return id(global_bambu_ip); + - text.set: + id: bambu_serial_number + value: !lambda |- + return id(global_bambu_serial); - if: condition: lambda: |- @@ -55,13 +80,13 @@ esphome: - logger.log: level: info format: "Connecting to Bambu printer" - - mqtt.enable: + - ${mqtt}.enable: id: bambu_mqtt else: - logger.log: level: info format: "Missing Bambu Credentials, skipping mqtt connect" - - mqtt.disable: + - ${mqtt}.disable: id: bambu_mqtt # - script.execute: set_all_leds_white # - lambda: |- @@ -75,14 +100,16 @@ esphome: on_shutdown: then: - script.execute: set_led_off -esp32: - framework: - type: esp-idf - version: 5.3.1 - platform_version: 6.9.0 # https://github.com/platformio/platform-espressif32/releases/ - sdkconfig_options: - CONFIG_MBEDTLS_HKDF_C: y # Needed for bambu KDF - CONFIG_MBEDTLS_MD_C: y # Needed for bambu KDF + + +# esp32: +# framework: +# type: esp-idf +# version: 5.3.1 +# platform_version: 6.9.0 # https://github.com/platformio/platform-espressif32/releases/ +# sdkconfig_options: +# CONFIG_MBEDTLS_HKDF_C: y # Needed for bambu KDF +# CONFIG_MBEDTLS_MD_C: y # Needed for bambu KDF # version: recommended # sdkconfig_options: # MBEDTLS_CERTIFICATE_BUNDLE: y @@ -105,9 +132,9 @@ packages: mqtt_bambu_lan: !include conf.d/mqtt_bambu_lan.yaml filament: !include conf.d/filament.yaml bambu_printer: !include conf.d/bambu_printer.yaml - pn532_rfid-solo: !include conf.d/pn532_rfid-solo.yaml + #pn532_rfid-solo: !include conf.d/pn532_rfid-solo.yaml # Comment if using an AMS with 4 LEDs; make sure you uncomment below also + pn532_rfid-ams-external: !include conf.d/pn532_rfid-ams-external.yaml # Uncomment if using an AMS with 4 LEDs; make sure you comment above also automation: !include conf.d/automation.yaml - led-external: !include conf.d/led-external.yaml ota: !include conf.d/ota.yaml #update: !include conf.d/update.yaml api: !include conf.d/api.yaml diff --git a/firmware/components/rp2040_mqtt/__init__.py b/firmware/components/rp2040_mqtt/__init__.py new file mode 100644 index 0000000..7b1dc53 --- /dev/null +++ b/firmware/components/rp2040_mqtt/__init__.py @@ -0,0 +1,153 @@ +# Copyright (C) 2025 Drew Green (@agreenbhm) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.automation import Condition +from esphome.const import CONF_ID +from esphome.core import CORE, coroutine_with_priority +from esphome import automation +from esphome.const import ( + CONF_TOPIC, + CONF_PAYLOAD, + CONF_QOS, + CONF_RETAIN +) + +rp2040_mqtt_ns = cg.esphome_ns.namespace("rp2040_mqtt") +RP2040MQTTComponent = rp2040_mqtt_ns.class_("RP2040MQTT", cg.Component) +RP2040MQTTConnectedCondition = rp2040_mqtt_ns.class_("RP2040MQTTConnectedCondition", Condition) +RP2040MQTTEnableAction = rp2040_mqtt_ns.class_("RP2040MQTTEnableAction", automation.Action) +RP2040MQTTDisableAction = rp2040_mqtt_ns.class_("RP2040MQTTDisableAction", automation.Action) +RP2040MQTTPublishAction = rp2040_mqtt_ns.class_("RP2040MQTTPublishAction", automation.Action) + +MULTI_CONF = True + +CONF_SECURE = "secure" +CONF_MQTT_HOST = "mqtt_host" +CONF_MQTT_PORT = "mqtt_port" +CONF_AUTH = "auth" +CONF_MQTT_USER = "mqtt_user" +CONF_MQTT_PASS = "mqtt_pass" + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RP2040MQTTComponent), + cv.Optional(CONF_MQTT_HOST): cv.string, + cv.Optional(CONF_MQTT_PORT): cv.port, + cv.Optional(CONF_SECURE): cv.boolean, + cv.Optional(CONF_AUTH): cv.boolean, + cv.Optional(CONF_MQTT_USER): cv.string, + cv.Optional(CONF_MQTT_PASS): cv.string + + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +@automation.register_action( + "rp2040_mqtt.enable", + RP2040MQTTEnableAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(RP2040MQTTComponent), + } + ), +) + +async def rp2040_mqtt_enable_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + +@automation.register_action( + "rp2040_mqtt.disable", + RP2040MQTTDisableAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(RP2040MQTTComponent), + } + ), +) + +async def rp2040_mqtt_disable_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + +MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(RP2040MQTTComponent), + cv.Required(CONF_TOPIC): cv.templatable(cv.publish_topic), + cv.Required(CONF_PAYLOAD): cv.templatable(cv.mqtt_payload), + cv.Optional(CONF_QOS, default=0): cv.templatable(cv.mqtt_qos), + cv.Optional(CONF_RETAIN, default=False): cv.templatable(cv.boolean), + } +) + + +@automation.register_action( + "rp2040_mqtt.publish", RP2040MQTTPublishAction, MQTT_PUBLISH_ACTION_SCHEMA +) +async def rp2040_mqtt_publish_action_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_TOPIC], args, cg.std_string) + cg.add(var.set_topic(template_)) + + template_ = await cg.templatable(config[CONF_PAYLOAD], args, cg.std_string) + cg.add(var.set_payload(template_)) + template_ = await cg.templatable(config[CONF_QOS], args, cg.uint8) + cg.add(var.set_qos(template_)) + template_ = await cg.templatable(config[CONF_RETAIN], args, bool) + cg.add(var.set_retain(template_)) + return var + + + +@automation.register_condition( + "rp2040_mqtt.connected", + RP2040MQTTConnectedCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(RP2040MQTTComponent), + } + ), +) +async def rp2040_mqtt_connected_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren) + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + if CONF_MQTT_HOST in config: + cg.add(var.set_mqtt_host(config[CONF_MQTT_HOST])) + if CONF_MQTT_PORT in config: + cg.add(var.set_mqtt_port(config[CONF_MQTT_PORT])) + if CONF_SECURE in config: + cg.add(var.set_secure(config[CONF_SECURE])) + if CONF_AUTH in config: + cg.add(var.set_authenticate(config[CONF_AUTH])) + if CONF_MQTT_USER in config: + cg.add(var.set_mqtt_user(config[CONF_MQTT_USER])) + if CONF_MQTT_PASS in config: + cg.add(var.set_mqtt_password(config[CONF_MQTT_PASS])) + # if CORE.is_rp2040: + # cg.add_library("mobizt/ESP_SSLClient", None) + cg.add_library("mobizt/ESP_SSLClient", None) + cg.add_library("arduino-libraries/ArduinoMqttClient", None) + + + yield cg.register_component(var, config) \ No newline at end of file diff --git a/firmware/components/rp2040_mqtt/rp2040_mqtt.cpp b/firmware/components/rp2040_mqtt/rp2040_mqtt.cpp new file mode 100644 index 0000000..9263ff6 --- /dev/null +++ b/firmware/components/rp2040_mqtt/rp2040_mqtt.cpp @@ -0,0 +1,115 @@ +/* Copyright (C) 2025 Drew Green (@agreenbhm) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "rp2040_mqtt.h" + +#include "esphome/core/log.h" +#include "esphome/core/util.h" +#include "esphome/components/network/util.h" + +namespace esphome +{ + namespace rp2040_mqtt + { + float RP2040MQTT::get_setup_priority() const { return setup_priority::AFTER_WIFI; } + static const char *TAG = "rp2040_mqtt"; + + void RP2040MQTT::setup() + { + if (!this->enabled_) + { + ESP_LOGE(TAG, "RP2040MQTT is disabled"); + return; + } + ESP_LOGCONFIG(TAG, "Setting up RP2040MQTT..."); + + this->ssl_client_.setInsecure(); + + this->ssl_client_.setBufferSizes(16384, 512); + this->ssl_client_.setDebugLevel(0); + this->ssl_client_.setClient(&this->wifiClient_, this->secure_); + + this->ssl_client_.setSessionTimeout(150); + + this->mqttClient_.stop(); + + if (this->authenticate_) + { + this->mqttClient_.setUsernamePassword(this->mqtt_user_, this->mqtt_password_.c_str()); + } + + ESP_LOGD(TAG, "Connecting MQTT to %s:%d", this->mqtt_host_.c_str(), this->mqtt_port_); + if (!(this->mqttClient_.connect(this->mqtt_host_.c_str(), this->mqtt_port_))) + { + ESP_LOGE(TAG, "Failed to connect to MQTT server"); + return; + } + else + { + ESP_LOGD(TAG, "Connected to MQTT server"); + this->connected_ = true; + } + } + + void RP2040MQTT::loop() + { + if (!this->enabled_) + { + return; + } + + if (!this->mqttClient_.connected()) + { + if (!this->waiting_) + { + this->mqttClient_.stop(); + ESP_LOGE(TAG, "MQTT client disconnected"); + this->connected_ = false; + this->startTime_ = millis(); + this->waiting_ = true; + } + else + { + if ((millis() - this->startTime_) > 15000) + { + this->waiting_ = false; + ESP_LOGE(TAG, "Reconnecting to MQTT server"); + this->setup(); + return; + } + } + + } else{ + this->mqttClient_.poll(); + //delay(10); + } + } + + void RP2040MQTT::dump_config() + { + ESP_LOGCONFIG(TAG, "Broker: %s:%u", this->mqtt_host_, this->mqtt_port_); + } + + void RP2040MQTT::publish(const std::string &topic, const std::string &payload, uint8_t qos, bool retain) + { + + this->mqttClient_.beginMessage(topic.c_str()); + this->mqttClient_.print(payload.c_str()); + this->mqttClient_.endMessage(); + } + + } // namespace rp2040_mqtt +} // namespace esphome \ No newline at end of file diff --git a/firmware/components/rp2040_mqtt/rp2040_mqtt.h b/firmware/components/rp2040_mqtt/rp2040_mqtt.h new file mode 100644 index 0000000..e7941f0 --- /dev/null +++ b/firmware/components/rp2040_mqtt/rp2040_mqtt.h @@ -0,0 +1,142 @@ +/* Copyright (C) 2025 Drew Green (@agreenbhm) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/automation.h" + +#include +//#include +#include +#include +#include + + +namespace esphome +{ + namespace rp2040_mqtt + { + class RP2040MQTT : public Component + { + public: + RP2040MQTT() = default; + + void setup() override; + void loop() override; + void dump_config() override; + + float get_setup_priority() const override; + + void set_mqtt_host(const char *mqtt_host) { this->mqtt_host_ = std::string(mqtt_host); } + + void set_broker_address(const char *mqtt_host) { this->set_mqtt_host(mqtt_host); } + + void set_mqtt_port(uint16_t mqtt_port) { this->mqtt_port_ = mqtt_port; } + + void set_secure(bool secure) { this->secure_ = secure; } + + void set_mqtt_user(const char *mqtt_user) { this->mqtt_user_ = mqtt_user; } + + void set_mqtt_password(const char *mqtt_password) { this->mqtt_password_ = std::string(mqtt_password); } + + void set_password(const char *password) { this->set_mqtt_password(password); } + + void set_authenticate(bool authenticate) { this->authenticate_ = authenticate; } + + void set_enabled(bool enabled) { this->enabled_ = enabled; } + + void set_connected(bool connected) { this->connected_ = connected; } + + bool is_connected() { return this->connected_; } + + void publish(const std::string &topic, const std::string &payload, uint8_t qos = 0, bool retain = false); + + void stop() { this->mqttClient_.stop(); } + + protected: + bool secure_{true}; + std::string mqtt_host_{}; + uint16_t mqtt_port_{0}; + const char *mqtt_user_{nullptr}; + std::string mqtt_password_{}; + bool authenticate_{false}; + bool enabled_{true}; + bool connected_{false}; + ESP_SSLClient ssl_client_{}; + MqttClient mqttClient_{ssl_client_}; + WiFiClient wifiClient_{}; + unsigned long startTime_{0}; + bool waiting_{false}; + + }; + + template class RP2040MQTTEnableAction : public Action + { + public: + RP2040MQTTEnableAction(RP2040MQTT *parent) : parent_(parent) {} + void play(Ts... x) override { + this->parent_->set_enabled(true); + this->parent_->setup(); + } + + protected: + RP2040MQTT *parent_; + }; + + template class RP2040MQTTDisableAction : public Action + { + public: + RP2040MQTTDisableAction(RP2040MQTT *parent) : parent_(parent) {} + void play(Ts... x) override { + this->parent_->set_enabled(false); + this->parent_->stop(); + this->parent_->set_connected(false); + } + + protected: + RP2040MQTT *parent_; + }; + + template class RP2040MQTTPublishAction : public Action { + public: + RP2040MQTTPublishAction(RP2040MQTT *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(std::string, topic) + TEMPLATABLE_VALUE(std::string, payload) + TEMPLATABLE_VALUE(uint8_t, qos) + TEMPLATABLE_VALUE(bool, retain) + + void play(Ts... x) override { + this->parent_->publish(this->topic_.value(x...), this->payload_.value(x...), this->qos_.value(x...), + this->retain_.value(x...)); + } + + protected: + RP2040MQTT *parent_; + }; + + template class RP2040MQTTConnectedCondition : public Condition { + public: + RP2040MQTTConnectedCondition(RP2040MQTT *parent) : parent_(parent) {} + bool check(Ts... x) override { return this->parent_->is_connected(); } + + protected: + RP2040MQTT *parent_; + }; + + } // namespace rp2040_mqtt +} // namespace esphome \ No newline at end of file diff --git a/firmware/conf.d/automation.yaml b/firmware/conf.d/automation.yaml index 1f124dd..ce3521e 100644 --- a/firmware/conf.d/automation.yaml +++ b/firmware/conf.d/automation.yaml @@ -55,19 +55,26 @@ script: return true; } - lambda: |- - if (id(filament_max_temp).state >= 300) { - ESP_LOGE("main", "Filament Min Temp must be less than 300"); + if (id(filament_max_temp).state > 300) { + ESP_LOGE("main", "Filament Max Temp must be less than 300"); return false; } else { return true; } - lambda: |- - if (id(filament_min_temp).state <= 150) { - ESP_LOGE("main", "Filament Max Temp must be greater than 150"); + if (id(filament_min_temp).state < 150) { + ESP_LOGE("main", "Filament Min Temp must be greater than 150"); return false; } else { return true; } + - lambda: |- + if (id(filament_min_temp).state > id(filament_max_temp).state) { + ESP_LOGE("main", "Filament Min Temp must be less than Filament Max Temp"); + return false; + } else { + return true; + } - lambda: |- if (id(filament_type).state == "") { ESP_LOGE("main", "Filament Type cannot be empty"); @@ -91,7 +98,7 @@ script: } # TODO: Check if mqtt is enabled once this is merged: https://github.com/esphome/esphome/pull/7716 then: - - mqtt.publish: + - ${mqtt}.publish: # topic: device/${bambu_serial_number}/request topic: !lambda 'return "device/" + id(bambu_serial_number).state + "/request";' payload: !lambda |- diff --git a/firmware/conf.d/bambu_printer.yaml b/firmware/conf.d/bambu_printer.yaml index 645876b..dfe63f3 100644 --- a/firmware/conf.d/bambu_printer.yaml +++ b/firmware/conf.d/bambu_printer.yaml @@ -29,6 +29,10 @@ text: sorting_group_id: sorting_group_printer_settings on_value: then: + - globals.set: + id: global_bambu_serial + value: !lambda |- + return x.c_str(); - if: condition: lambda: |- @@ -39,13 +43,13 @@ text: - logger.log: level: info format: "Connecting to Bambu printer" - - mqtt.enable: + - ${mqtt}.enable: id: bambu_mqtt else: - logger.log: level: info format: "Missing Bambu Credentials, skipping mqtt connect" - - mqtt.disable: + - ${mqtt}.disable: id: bambu_mqtt # - script.execute: check_mqtt_creds #TODO: Enable once this is merged: https://github.com/esphome/esphome/pull/7716 # lambda: |- @@ -64,6 +68,10 @@ text: then: - lambda: |- id(bambu_mqtt).set_password(x.c_str()); + - globals.set: + id: global_lan_access_code + value: !lambda |- + return x.c_str(); - if: condition: lambda: |- @@ -74,13 +82,13 @@ text: - logger.log: level: info format: "Connecting to Bambu printer" - - mqtt.enable: + - ${mqtt}.enable: id: bambu_mqtt else: - logger.log: level: info format: "Missing Bambu Credentials, skipping mqtt connect" - - mqtt.disable: + - ${mqtt}.disable: id: bambu_mqtt - platform: template name: Printer IP Address @@ -96,6 +104,10 @@ text: then: - lambda: |- id(bambu_mqtt).set_broker_address(x.c_str()); + - globals.set: + id: global_bambu_ip + value: !lambda |- + return x.c_str(); - if: condition: lambda: |- @@ -106,11 +118,11 @@ text: - logger.log: level: info format: "Connecting to Bambu printer" - - mqtt.enable: + - ${mqtt}.enable: id: bambu_mqtt else: - logger.log: "Missing Bambu Credentials, skipping mqtt connect" - - mqtt.disable: + - ${mqtt}.disable: id: bambu_mqtt button: diff --git a/firmware/conf.d/button.yaml b/firmware/conf.d/button.yaml index 048e5ae..67499f2 100644 --- a/firmware/conf.d/button.yaml +++ b/firmware/conf.d/button.yaml @@ -11,18 +11,18 @@ binary_sensor: inverted: true on_press: then: - - script.execute: - id: set_led_red - led_number: -1 + # - script.execute: + # id: set_led_red + # led_number: -1 - globals.set: id: button_press_duration value: "0" - script.execute: start_timer on_release: then: - - script.execute: - id: set_led_white - led_number: -1 + # - script.execute: + # id: set_led_white + # led_number: -1 - script.stop: timer_script - if: condition: @@ -61,4 +61,4 @@ button: name: "Factory Reset" id: factory_reset_openspool state_topic: - internal: true + internal: true \ No newline at end of file diff --git a/firmware/conf.d/debug.yaml b/firmware/conf.d/debug.yaml index db777b1..5ff8afa 100644 --- a/firmware/conf.d/debug.yaml +++ b/firmware/conf.d/debug.yaml @@ -26,18 +26,18 @@ text_sensor: reset_reason: name: "Reset Reason" id: reset_reason - state_topic: + #state_topic: sensor: - platform: debug free: name: "Heap Free" id: heap_free - state_topic: + #state_topic: block: name: "Heap Max Block" id: heap_max_block - state_topic: - # loop_time: - # name: "Loop Time" - # id: loop_time - # state_topic: + # state_topic: + # loop_time: + # name: "Loop Time" + # id: loop_time + # state_topic: diff --git a/firmware/conf.d/filament.yaml b/firmware/conf.d/filament.yaml index 3a6d03b..64f789f 100644 --- a/firmware/conf.d/filament.yaml +++ b/firmware/conf.d/filament.yaml @@ -215,6 +215,7 @@ number: state_topic: min_value: 150 max_value: 300 + initial_value: 150 unit_of_measurement: °C step: 5 icon: mdi:thermometer-low diff --git a/firmware/conf.d/improv-serial.yaml b/firmware/conf.d/improv-serial.yaml index a61cf62..ec99a68 100644 --- a/firmware/conf.d/improv-serial.yaml +++ b/firmware/conf.d/improv-serial.yaml @@ -1,2 +1,3 @@ --- -improv_serial: # next_url: "http://{{ip_address}}" +improv_serial: +# next_url: "http://{{ip_address}}" diff --git a/firmware/conf.d/led-external.yaml b/firmware/conf.d/led-external.yaml index eaa0337..3f975c5 100644 --- a/firmware/conf.d/led-external.yaml +++ b/firmware/conf.d/led-external.yaml @@ -1,6 +1,6 @@ --- substitutions: - led_count: '9' + led_count: '4' light: - platform: esp32_rmt_led_strip name: LEDs diff --git a/firmware/conf.d/led-external_rp2040.yaml b/firmware/conf.d/led-external_rp2040.yaml new file mode 100644 index 0000000..d2de288 --- /dev/null +++ b/firmware/conf.d/led-external_rp2040.yaml @@ -0,0 +1,162 @@ +--- +substitutions: + led_count: '4' +light: +- platform: rp2040_pio_led_strip + name: LEDs + id: neopixel_light + pin: ${led_pin} + pio: 0 + num_leds: ${led_count} + rgb_order: GRB + chipset: WS2812 + default_transition_length: 0.4s + restore_mode: RESTORE_DEFAULT_ON + entity_category: diagnostic + icon: mdi:led-strip + effects: + - addressable_rainbow: + name: Rainbow + speed: 25 + width: 15 + - addressable_lambda: + name: Breathing Blue + update_interval: 10ms + lambda: |- + static float b = 0; + b = (sin(millis() / 500.0) + 1.0) / 2.0 * 0.6 + 0.4; + auto color = esphome::light::ESPColor(0, 0, uint8_t(255 * b)); + it.all() = color; + - addressable_lambda: + name: Apple Breathing + update_interval: 10ms + lambda: |- + const float cPI = 3.14159265359; + //TODO: 42.546 should be replaced by 83.333 + float t = millis() / 2000.0; + float brightness_float = (exp(sin(t * cPI)) - 0.368) * 42.546; + uint8_t brightness = uint8_t(brightness_float); + auto color = esphome::light::ESPColor(brightness, 0, 0); + for (int i = 0; i < it.size(); ++i) { + it[i] = color; + } + # - strobe: + # name: "Data Upload" + # colors: + # - state: TRUE + # brightness: 100% + # duration: 50ms + # red: 0% + # green: 100% + # blue: 0% + # - state: FALSE + # duration: 50ms +script: +- id: set_led_red + parameters: + led_number: int8_t + then: + - light.addressable_set: + id: neopixel_light + color_brightness: 100% + range_from: !lambda "return led_number < 0 ? 0 : led_number;" + range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" + red: 100% + green: 0% + blue: 0% +- id: set_led_green + parameters: + led_number: int8_t + then: + - light.addressable_set: + id: neopixel_light + color_brightness: 100% + range_from: !lambda "return led_number < 0 ? 0 : led_number;" + range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" + red: 0% + green: 100% + blue: 0% +- id: set_led_blue + parameters: + led_number: int8_t + then: + - light.addressable_set: + id: neopixel_light + color_brightness: 100% + range_from: !lambda "return led_number < 0 ? 0 : led_number;" + range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" + red: 0% + green: 0% + blue: 100% +- id: set_led_yellow + parameters: + led_number: int8_t + then: + - light.addressable_set: + id: neopixel_light + color_brightness: 100% + range_from: !lambda "return led_number < 0 ? 0 : led_number;" + range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" + red: 100% + green: 100% + blue: 0% +- id: set_led_white + parameters: + led_number: int8_t + then: + - light.addressable_set: + id: neopixel_light + color_brightness: 50% + range_from: !lambda "return led_number < 0 ? 0 : led_number;" + range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" + red: 100% + green: 100% + blue: 100% +- id: set_all_leds_white + then: + - light.turn_on: + id: neopixel_light + effect: none + brightness: 50% + red: 100% + green: 100% + blue: 100% +- id: set_led_off + then: + - light.turn_on: + id: neopixel_light + brightness: 0% + red: 0% + green: 0% + blue: 0% + effect: none + - delay: 50ms + - light.turn_off: + id: neopixel_light + transition_length: 0s +- id: set_led_rainbow + then: + - light.turn_on: + id: neopixel_light + effect: Rainbow +- id: set_led_breathing_blue + then: + - light.turn_on: + id: neopixel_light + effect: none + - light.turn_on: + id: neopixel_light + effect: Breathing Blue + brightness: 100% +- id: set_led_breathing_green + then: + - light.turn_on: + id: neopixel_light + effect: Breathing Green + brightness: 100% +- id: set_led_apple_breathing + then: + - light.turn_on: + id: neopixel_light + effect: Apple Breathing + brightness: 100% diff --git a/firmware/conf.d/logger.yaml b/firmware/conf.d/logger.yaml index 27c1ca8..2397401 100644 --- a/firmware/conf.d/logger.yaml +++ b/firmware/conf.d/logger.yaml @@ -1,44 +1,44 @@ logger: - level: VERBOSE + level: DEBUG logs: - sensor: INFO - pn532: DEBUG - pn532_spi: DEBUG - spi: INFO - nfc: DEBUG - i2c: INFO - mqtt: DEBUG - mqtt.component: DEBUG - mqtt.client: DEBUG - wifi: INFO - wifi_esp32: INFO - esp-tls: DEBUG - esp-tls-mbedtls: DEBUG - http_client: INFO - http_request: INFO - scheduler: INFO - text_sensor: INFO - text_sensor.filter: INFO - esp32_rmt_led_strip: INFO - debug: INFO - web_server: DEBUG - light: INFO - light.addressable: INFO - api: INFO - improv_serial: VERBOSE - json: INFO - esp32.preferences: INFO - esp-idf: DEBUG - component: VERBOSE - binary_sensor: INFO - template.text: DEBUG - text: DEBUG + sensor: WARN + pn532: INFO + pn532_spi: INFO + spi: WARN + nfc: WARN + i2c: WARN + mqtt: INFO + mqtt.component: INFO + mqtt.client: INFO + wifi: DEBUG + wifi_esp32: WARN + esp-tls: WARN + esp-tls-mbedtls: WARN + http_client: WARN + http_request: WARN + scheduler: WARN + text_sensor: WARN + text_sensor.filter: WARN + esp32_rmt_led_strip: WARN + WARN: WARN + web_server: WARN + light: WARN + light.addressable: WARN + api: WARN + improv_serial: WARN + json: WARN + esp32.preferences: WARN + esp-idf: WARN + component: WARN + binary_sensor: WARN + template.text: WARN + text: WARN spi_device: ERROR - nfc.ndef_message: DEBUG - select: INFO - number: INFO - pn532.mifare_ultralight: VERBOSE - pn532.mifare_classic: VERBOSE - TAG: VERBOSE - NFC: VERBOSE + nfc.ndef_message: WARN + select: WARN + number: WARN + pn532.mifare_ultralight: INFO + pn532.mifare_classic: INFO + TAG: INFO + NFC: INFO "": ERROR diff --git a/firmware/conf.d/mqtt_bambu_lan.yaml b/firmware/conf.d/mqtt_bambu_lan.yaml index 540d5f3..585c7f1 100644 --- a/firmware/conf.d/mqtt_bambu_lan.yaml +++ b/firmware/conf.d/mqtt_bambu_lan.yaml @@ -116,4 +116,4 @@ binary_sensor: # name: "Data from topic" # id: mysensor # topic: testtopic/1234 -# state_topic: # Don't post update to MQTT +# state_topic: # Don't post update to MQTT \ No newline at end of file diff --git a/firmware/conf.d/pn532_rfid-ams-external.yaml b/firmware/conf.d/pn532_rfid-ams-external.yaml new file mode 100644 index 0000000..50c9503 --- /dev/null +++ b/firmware/conf.d/pn532_rfid-ams-external.yaml @@ -0,0 +1,411 @@ +--- +substitutions: + spi_data_rate: 200kHz + # spi_data_rate: 1Mhz #TODO: decrease this to increase reliability for long cable runs + # update_interval: "500ms" + # update_interval: "2s" + # update_interval: "1s" + # update_interval: "1500ms" + update_interval: "750ms" + +globals: + - id: global_tap_counter + type: int + restore_value: no + initial_value: '0' + - id: global_error + type: bool + restore_value: no + initial_value: 'false' + +interval: + - id: tap_timer + interval: 1500ms + startup_delay: 3s + then: + - lambda: |- + if(id(global_tap_counter) == 5) { + auto disable_rainbow = id(neopixel_light).turn_on(); + disable_rainbow.set_effect("none"); + disable_rainbow.perform(); + } else{ + id(set_led_green).execute(id(global_tap_counter)); + } + + id(global_tap_counter) += 1; + if(id(global_tap_counter) == 5) { + id(set_led_rainbow).execute(); + } + if (id(global_tap_counter) > 5) { + id(global_tap_counter) = 0; // Reset to 0 (all white LEDs) + for(int i = 0; i < 4; i++) { + id(set_led_white).execute(i); + } + } + ESP_LOGD("NFC", "Counter incremented: %d", id(global_tap_counter)); + +binary_sensor: +- platform: template + name: "NFC Tag Present" + id: nfc_tag_present0 + state_topic: + icon: mdi:circle-double + web_server: + sorting_group_id: sorting_group_rfid +spi: +# On esp32-s3 SPI2 a can support 6 devices, whereas only 3 on bus SPI3 +- id: SPI2 + clk_pin: ${spi2_clk_pin} # SCK + miso_pin: ${spi2_miso_pin} # MO/SDA/TX (MISO) + mosi_pin: ${spi2_mosi_pin} # M (MOSI) + interface: ${spi2_type} +pn532_spi: +- id: rfid_reader_spi_0 + cs_pin: ${rfid0_ss_pin} # NSS/SCL/RX + spi_id: ${rfid0_spi_interface} + data_rate: ${spi_data_rate} + update_interval: ${update_interval} + on_tag_removed: + then: + - component.suspend: tap_timer + - binary_sensor.template.publish: + id: nfc_tag_present0 + state: OFF + - light.turn_on: + id: neopixel_light + effect: none + # - script.execute: + # id: set_led_white + # led_number: -1 + - delay: 500ms + - lambda: |- + id(tap_timer).setup(); + if(id(global_error) == false) { + if(id(global_tap_counter) == 5){ + id(set_led_rainbow).execute(); + } else{ + for(int i = 0; i < id(global_tap_counter); i++) { + id(set_led_green).execute(i); + } + } + } + - delay: 500ms + - light.turn_on: + id: neopixel_light + effect: none + - script.execute: + id: set_led_white + led_number: -1 + - if: + condition: + and: + - lambda: 'return id(global_error) == false;' + - lambda: 'return id(global_tap_counter) > 0;' + - ${mqtt}.connected + - lambda: 'return id(filament_raw_data0).state != "";' + - lambda: 'return id(filament_raw_data0).state != "{}";' + - binary_sensor.is_on: rfid_reader_spi_0_tag_is_openspool + - binary_sensor.is_on: rfid_reader_spi_0_tag_parsed + then: + - ${mqtt}.publish: + topic: !lambda 'return "device/" + id(bambu_serial_number).state + "/request";' + # 255 = external ams, TODO: A1,P1,X1 might use different values for AMS + # 254 = external tray, first filament slot is tray 0 + # payload: !lambda "return bambulabs::generate_mqtt_payload( id(filament_raw_data0).state, 255, 254 );" + payload: !lambda "return bambulabs::generate_mqtt_payload( id(filament_raw_data0).state, id(global_tap_counter) == 5 ? 255 : 0, id(global_tap_counter) == 5 ? 254: id(global_tap_counter) - 1 );" + - lambda: 'id(filament_raw_data0).publish_state("");' + - globals.set: + id: global_error + value: 'false' + + + on_tag: + then: + - binary_sensor.template.publish: + id: nfc_tag_present0 + state: ON + - lambda: |- + bool is_valid_openspool = false; + id(global_error) = false; + std::string payload; + + if (tag.has_ndef_message()) { + auto records = tag.get_ndef_message()->get_records(); + bool found_json = false; + + // Look for application/json record + for (const auto& record : records) { + if (record->get_type() == "application/json") { + if (found_json) { + // More than one JSON record found + ESP_LOGW("NFC", "Multiple JSON records found, using first one"); + break; + } + + payload = record->get_payload(); + ESP_LOGD("NFC", "Payload: %s", payload.c_str()); + id(filament_raw_data0).publish_state(payload); + + // Parse JSON and check for OpenSpool protocol + auto parse_result = json::parse_json(payload, [&](JsonObject root) { + is_valid_openspool = root["protocol"] == "openspool"; + return true; + }); + + if (!parse_result) { + ESP_LOGE("NFC", "Failed to parse JSON payload"); + } + + found_json = true; + } + } + + if (!found_json) { + ESP_LOGW("NFC", "No application/json record found"); + } + } else { + ESP_LOGI("NFC", "Tag found without NDEF message"); + } + + // Update states + id(rfid_reader_spi_0_tag_parsed).publish_state(true); + id(rfid_reader_spi_0_tag_is_openspool).publish_state(is_valid_openspool); + + // Set LED color + if (is_valid_openspool) { + id(tap_timer).start_poller(); + id(global_tap_counter) = 0; + //TODO: Generate and send MQTT message here + if (!payload.empty()) { + } + } else { + id(tap_timer).stop_poller(); + id(tap_timer).setup(); + id(global_error) = true; + for(int i = 0; i < 4; i++) { + id(set_led_red).execute(i); + } + } + # - if: + # condition: + # and: + # - ${mqtt}.connected + # - lambda: 'return id(filament_raw_data0).state != "";' + # - lambda: 'return id(filament_raw_data0).state != "{}";' + # - binary_sensor.is_on: rfid_reader_spi_0_tag_is_openspool + # - binary_sensor.is_on: rfid_reader_spi_0_tag_parsed + # then: + # - ${mqtt}.publish: + # topic: !lambda 'return "device/" + id(bambu_serial_number).state + "/request";' + # # 255 = external ams, TODO: A1,P1,X1 might use different values for AMS + # # 254 = external tray, first filament slot is tray 0 + # payload: !lambda "return bambulabs::generate_mqtt_payload( id(filament_raw_data0).state, 255, 254 );" +text_sensor: +- platform: template + name: "NFC Raw Data" + id: filament_raw_data0 + state_topic: + internal: false # Always show raw data 0 + icon: mdi:nfc-variant + web_server: + sorting_group_id: sorting_group_rfid + filters: + - lambda: |- + auto pretty_json = [](const std::string &x) -> std::string { + if (x.empty()) { + ESP_LOGD("NFC", "Input string is empty"); + return x; + } + ESP_LOGD("NFC", "Input string: %s", x.c_str()); + + StaticJsonDocument<1024> doc; // Use StaticJsonDocument for memory efficiency + DeserializationError error = deserializeJson(doc, x); + if (error) { + ESP_LOGE("NFC", "JSON parsing failed: %s", error.c_str()); + return ""; + } + + if (!doc.is()) { + ESP_LOGE("NFC", "Invalid JSON: Not an object"); + return ""; + } + + const char* required_fields[] = {"protocol", "color_hex", "type", "min_temp", "max_temp", "brand"}; + for (const char* field : required_fields) { + if (!doc.containsKey(field)) { + ESP_LOGE("NFC", "Invalid JSON: Missing required field '%s'", field); + return ""; + } + } + + std::string output; + serializeJsonPretty(doc, output); + + if (output.length() > 1024) { + ESP_LOGE("NFC", "Prettified JSON exceeds 1024 bytes"); + return ""; + } + + return output; + }; + return pretty_json(x); + +- platform: template + name: "NFC Preview" + id: nfc_preview + state_topic: + icon: mdi:nfc-search-variant + web_server: + sorting_group_id: sorting_group_rfid +# Create virtual button that can be pressed in the gui +button: +- platform: template + name: "Write NFC" + id: write_nfc + state_topic: + icon: mdi:nfc-tap + web_server: + sorting_group_id: sorting_group_filament_settings + sorting_weight: 210 + on_press: + then: + - if: + condition: + and: + # - lambda: |- #TODO: I'm not sure that tag_present registers on blank tags + # if (!id(nfc_tag_present0).state) { + # ESP_LOGE("main", "NFC Tag not present on RFID Reader 0"); + # return false; + # } else { + # return true; + # } + - lambda: |- + if (id(filament_brand).state == "") { + ESP_LOGE("main", "Filament Brand cannot be empty"); + return false; + } else { + return true; + } + - lambda: |- + if (id(filament_brand_code).state == "") { + ESP_LOGE("main", "Filament Brand Code cannot be empty"); + return false; + } else { + return true; + } + - lambda: |- + if (id(filament_color_hex).state == "") { + ESP_LOGE("main", "Filament Color Hex cannot be empty"); + return false; + } else { + return true; + } + - lambda: |- + if (id(filament_max_temp).state > 300) { + ESP_LOGE("main", "Filament Max Temp must be less than 300"); + return false; + } else { + return true; + } + - lambda: |- + if (id(filament_min_temp).state < 150) { + ESP_LOGE("main", "Filament Min Temp must be greater than 150"); + return false; + } else { + return true; + } + - lambda: |- + if (id(filament_min_temp).state > id(filament_max_temp).state) { + ESP_LOGE("main", "Filament Min Temp must be less than Filament Max Temp"); + return false; + } else { + return true; + } + - lambda: |- + if (id(filament_type).state == "") { + ESP_LOGE("main", "Filament Type cannot be empty"); + return false; + } else { + return true; + } + then: + - lambda: |- + auto message = new nfc::NdefMessage(); + + //TODO: uncomment once esphome suppports messages nless than 255 bytes + // URI NDEF record + // esphome fails if this is more than 255 bytes + //auto uri_record = std::make_unique(); + //std::string url = "https://openspool.io/tag?"; + //url.append("protocol=").append("openspool"); + //url.append("&version=").append("1.0"); + //url.append("&color_hex=").append(id(filament_color_hex).state); + //url.append("&type=").append(id(filament_type).state); + //url.append("&min_temp=").append(std::to_string(static_cast(id(filament_min_temp).state))); + //url.append("&max_temp=").append(std::to_string(static_cast(id(filament_max_temp).state))); + //url.append("&brand=").append(id(filament_brand).state); + + // Warn if the url is more than 255 bytes + // While NFC allows messages longer than 255, you have to set the tnf_byte which esphome doesn't allow yet + //if (url.length() > 255) { + // ESP_LOGE("NFC", "URL longer than 255 bytes: %s", url.c_str()); + // return; + //} + //ESP_LOGD("NFC", "Adding NDEF Record URI: %s", url.c_str()); + //message->add_uri_record(url); + + // Application/Json NDEF record + auto json_record = std::make_unique(); + json_record->set_tnf(nfc::TNF_MIME_MEDIA); + json_record->set_type("application/json"); + + DynamicJsonDocument doc(256); // Adjust size as needed + JsonObject root = doc.to(); + root["version"] = "1.0"; + root["protocol"] = "openspool"; + if (id(filament_include_alpha).state == true){ + root["color_hex"] = id(filament_color_hexaa).state; + } + else{ + root["color_hex"] = id(filament_color_hex).state; + } + root["type"] = id(filament_type).state; + root["min_temp"] = id(filament_min_temp).state; + root["max_temp"] = id(filament_max_temp).state; + root["brand"] = id(filament_brand).state; + + std::string json_string; + serializeJson(root, json_string); + if (json_string.empty()) { + ESP_LOGE("rfid", "Failed to serialize JSON"); + return; + } + ESP_LOGI("rfid", "JSON content to be written: %s", json_string.c_str()); + json_record->set_payload(json_string); + message->add_record(std::move(json_record)); + + std::vector encoded_message = message->encode(); + size_t message_size = encoded_message.size(); + ESP_LOGI("rfid", "NDEF message size: %zu bytes", message_size); + + // TODO: make dynamic for NTAG215 (544) or NTAG216 (888) + // TODO: 544 bytes is actuall 496 usable according to logs + //const size_t MAX_MESSAGE_SIZE = 496; + //if (message_size > MAX_MESSAGE_SIZE) { + // ESP_LOGE("rfid", "Error: NDEF message size (%zu bytes) exceeds maximum allowed size (%zu bytes)", message_size, MAX_MESSAGE_SIZE); + //return; // Exit the function or throw an exception, depending on your error handling strategy + //} + + id(rfid_reader_spi_0).write_mode(message); + ESP_LOGI("rfid", "Writing JSON NDEF message to tag"); + - wait_until: + not: + pn532.is_writing: + id: rfid_reader_spi_0 + - logger.log: + tag: "rfid" + format: "Finished writing tag" + level: INFO + else: + - lambda: |- + ESP_LOGI("rfid", "NFC Tag not present"); diff --git a/firmware/conf.d/pn532_rfid-solo.yaml b/firmware/conf.d/pn532_rfid-solo.yaml index 4a11a4a..aed0fe8 100644 --- a/firmware/conf.d/pn532_rfid-solo.yaml +++ b/firmware/conf.d/pn532_rfid-solo.yaml @@ -101,13 +101,13 @@ pn532_spi: - if: condition: and: - - mqtt.connected + - ${mqtt}.connected - lambda: 'return id(filament_raw_data0).state != "";' - lambda: 'return id(filament_raw_data0).state != "{}";' - binary_sensor.is_on: rfid_reader_spi_0_tag_is_openspool - binary_sensor.is_on: rfid_reader_spi_0_tag_parsed then: - - mqtt.publish: + - ${mqtt}.publish: topic: !lambda 'return "device/" + id(bambu_serial_number).state + "/request";' # 255 = external ams, TODO: A1,P1,X1 might use different values for AMS # 254 = external tray, first filament slot is tray 0 @@ -213,15 +213,22 @@ button: return true; } - lambda: |- - if (id(filament_max_temp).state >= 300) { - ESP_LOGE("main", "Filament Min Temp must be less than 300"); + if (id(filament_max_temp).state > 300) { + ESP_LOGE("main", "Filament Max Temp must be less than 300"); return false; } else { return true; } - lambda: |- - if (id(filament_min_temp).state <= 150) { - ESP_LOGE("main", "Filament Max Temp must be greater than 150"); + if (id(filament_min_temp).state < 150) { + ESP_LOGE("main", "Filament Min Temp must be greater than 150"); + return false; + } else { + return true; + } + - lambda: |- + if (id(filament_min_temp).state > id(filament_max_temp).state) { + ESP_LOGE("main", "Filament Min Temp must be less than Filament Max Temp"); return false; } else { return true; diff --git a/firmware/conf.d/state_topic.yaml b/firmware/conf.d/state_topic.yaml new file mode 100644 index 0000000..65779ff --- /dev/null +++ b/firmware/conf.d/state_topic.yaml @@ -0,0 +1,84 @@ +select: + - id: !extend filament_type + state_topic: !remove + - id: !extend bambu_model + state_topic: !remove + - id: !extend filament_variant + state_topic: !remove + - id: !extend filament_brand + state_topic: !remove + - id: !extend filament_color + state_topic: !remove + +sensor: + - id: !extend openspool_uptime + state_topic: !remove + +text: + - id: !extend bambu_serial_number + state_topic: !remove + - id: !extend bambu_lan_access_code + state_topic: !remove + - id: !extend bambu_ip_address + state_topic: !remove + +text_sensor: + - id: !extend openspool_version + state_topic: !remove + - id: !extend openspool_url + state_topic: !remove + - id: !extend bambuhandy_url + state_topic: !remove + - id: !extend filament_brand_code + state_topic: !remove + - id: !extend filament_color_hex + state_topic: !remove + - id: !extend filament_color_hexaa + state_topic: !remove + - id: !extend filament_sub_brand + state_topic: !remove + - id: !extend filament_alpha + state_topic: !remove + - id: !extend filament_raw_data0 + state_topic: !remove + - id: !extend nfc_preview + state_topic: !remove + + +binary_sensor: + - id: !extend rfid_reader_spi_0_tag_parsed + state_topic: !remove + - id: !extend rfid_reader_spi_0_tag_is_openspool + state_topic: !remove + - id: !extend physical_button + state_topic: !remove + - id: !extend mqtt_connected + state_topic: !remove + - id: !extend nfc_tag_present0 + state_topic: !remove + +button: + - id: !extend restart_openspool + state_topic: !remove + - id: !extend factory_reset_openspool + state_topic: !remove + - id: !extend write_url_button + state_topic: !remove + - id: !extend write_bambuhandy_url_button + state_topic: !remove + - id: !extend upload_filament_settings + state_topic: !remove + - id: !extend write_nfc + state_topic: !remove + +switch: + - id: !extend filament_include_alpha + state_topic: !remove + +number: + - id: !extend filament_min_temp + state_topic: !remove + - id: !extend filament_max_temp + state_topic: !remove + - id: !extend filament_alpha_int + state_topic: !remove \ No newline at end of file diff --git a/firmware/conf.d/status_led.yaml b/firmware/conf.d/status_led.yaml index 3c575fe..37c2a5c 100644 --- a/firmware/conf.d/status_led.yaml +++ b/firmware/conf.d/status_led.yaml @@ -2,5 +2,5 @@ output: - id: onboard_led platform: gpio - pin: 15 + pin: 0 inverted: false diff --git a/firmware/conf.d/wifi.yaml b/firmware/conf.d/wifi.yaml index 1a9ccf1..644ab72 100644 --- a/firmware/conf.d/wifi.yaml +++ b/firmware/conf.d/wifi.yaml @@ -1,8 +1,12 @@ --- wifi: + id: openspool_wifi ap: ssid: "OpenSpool" + password: "OpenSpool" reboot_timeout: 0s # This must stay at 0s if api and mqtt are enabled, otherwise it will reboot every 15 minutes + # ssid: !secret ssid + # password: !secret password #TODO: if it is in ap mode, then pulse blue # on_connect: # then: @@ -23,7 +27,7 @@ text_sensor: mac_address: name: Mac Address id: mac_address - state_topic: + #state_topic: icon: mdi:network # - platform: wifi_info # ip_address: diff --git a/firmware/esp32-s3-devkitc-1.yaml b/firmware/esp32-s3-devkitc-1.yaml index 5676d0c..a4a7043 100644 --- a/firmware/esp32-s3-devkitc-1.yaml +++ b/firmware/esp32-s3-devkitc-1.yaml @@ -48,6 +48,8 @@ packages: #improv-bluetooth: !include conf.d/improv-bluetooth.yaml #led-internal: !include conf.d/led-internal.yaml extra: !include conf.d/extra.yaml + esp32-common: !include esp32_common.yaml + dashboard_import: package_import_url: github://spuder/openspool/firmware/esp32-s3-devkitc-1.yaml@main diff --git a/firmware/esp32_common.yaml b/firmware/esp32_common.yaml new file mode 100644 index 0000000..21b7105 --- /dev/null +++ b/firmware/esp32_common.yaml @@ -0,0 +1,18 @@ +esp32: + framework: + type: esp-idf + version: 5.3.1 + platform_version: 6.9.0 # https://github.com/platformio/platform-espressif32/releases/ + sdkconfig_options: + CONFIG_MBEDTLS_HKDF_C: y # Needed for bambu KDF + CONFIG_MBEDTLS_MD_C: y # Needed for bambu KDF + # version: recommended + # sdkconfig_options: + # MBEDTLS_CERTIFICATE_BUNDLE: y + # MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL: y + +packages: + led-external: !include conf.d/led-external.yaml + +substitutions: + mqtt: "mqtt" diff --git a/firmware/esp32_s3_super_mini.yaml b/firmware/esp32_s3_super_mini.yaml new file mode 100644 index 0000000..fd383f5 --- /dev/null +++ b/firmware/esp32_s3_super_mini.yaml @@ -0,0 +1,62 @@ +esphome: + on_boot: + then: + - component.suspend: tap_timer # Remove this if only using the spool holder; it's for AMS-external only + +esp32: + board: esp32-s3-devkitc-1 + +substitutions: + hide_ams_sensors: 'true' + led_pin: GPIO9 + neopixel_pin: '48' #some boards have this on pin 38 + spi2_type: SPI2 + spi2_clk_pin: GPIO10 + spi2_miso_pin: GPIO11 + spi2_mosi_pin: GPIO12 + + rfid0_spi_interface: SPI2 + rfid0_ss_pin: GPIO13 + +# rfid1_spi_interface: SPI2 +# rfid1_ss_pin: GPIO05 + +# rfid2_spi_interface: SPI2 +# rfid2_ss_pin: GPIO06 + +# rfid3_spi_interface: SPI2 +# rfid3_ss_pin: GPIO07 + + # rfid4_spi_interface: SPI2 + # rfid4_ss_pin: GPIO05 + + # rfid5_spi_interface: SPI2 + # rfid5_ss_pin: GPIO04 + + # spi3_type: SPI3 + # spi3_clk_pin: GPIO18 + # spi3_miso_pin: GPIO17 + # spi3_mosi_pin: GPIO16 + + # rfid6_spi_interface: SPI3 + # rfid6_ss_pin: GPIO15 + + # rfid7_spi_interface: SPI3 + # rfid7_ss_pin: GPIO9 + + # rfid8_spi_interface: SPI3 + # rfid8_ss_pin: GPI14 # TODO: Verify this is compatible + +packages: + #openspool-ams: !include openspool-ams.yaml + openspool-mini: !include openspool-mini.yaml + improv-serial: !include conf.d/improv-serial.yaml + esp32-common: !include esp32_common.yaml + #improv-bluetooth: !include conf.d/improv-bluetooth.yaml + led-internal: !include conf.d/led-internal.yaml + extra: !include conf.d/extra.yaml + button: !include conf.d/button.yaml + +dashboard_import: + package_import_url: github://spuder/openspool/firmware/esp32_s3_super_mini.yaml@main + import_full_config: false diff --git a/firmware/fix_compile.sh b/firmware/fix_compile.sh new file mode 100755 index 0000000..ab62bf3 --- /dev/null +++ b/firmware/fix_compile.sh @@ -0,0 +1 @@ +mkdir -p .esphome/build/rpipicow/.pioenvs/rpipicow diff --git a/firmware/lolin_s2_mini.yaml b/firmware/lolin_s2_mini.yaml index 7c7a48e..a34892b 100644 --- a/firmware/lolin_s2_mini.yaml +++ b/firmware/lolin_s2_mini.yaml @@ -24,6 +24,8 @@ packages: #improv-serial: !include conf.d/improv-serial.yaml #extra: !include conf.d/extra.yaml button: !include conf.d/button.yaml + esp32-common: !include esp32_common.yaml + dashboard_import: package_import_url: github://spuder/openspool/firmware/lolin_s2_mini.yaml@main diff --git a/firmware/lolin_s3_mini.yaml b/firmware/lolin_s3_mini.yaml index 756e488..5672304 100644 --- a/firmware/lolin_s3_mini.yaml +++ b/firmware/lolin_s3_mini.yaml @@ -19,6 +19,8 @@ packages: #improv-bluetooth: !include conf.d/improv-bluetooth.yaml extra: !include conf.d/extra.yaml button: !include conf.d/button.yaml + esp32-common: !include esp32_common.yaml + dashboard_import: package_import_url: github://spuder/openspool/firmware/lolin_s3_mini.yaml@main diff --git a/firmware/patches/0001-Updated-web-server-and-captive-portal-to-support-RP2.patch b/firmware/patches/0001-Updated-web-server-and-captive-portal-to-support-RP2.patch new file mode 100644 index 0000000..851930e --- /dev/null +++ b/firmware/patches/0001-Updated-web-server-and-captive-portal-to-support-RP2.patch @@ -0,0 +1,129 @@ +From 99adf9169a58ced761f055b563f4f61c7188ee02 Mon Sep 17 00:00:00 2001 +From: Drew Green +Date: Sun, 23 Feb 2025 18:13:39 -0500 +Subject: [PATCH] Updated web server and captive portal to support RP2040 + +--- + async_tcp/__init__.py | 6 ++++++- + captive_portal/__init__.py | 7 ++++++- + web_server/__init__.py | 4 +++- + web_server_base/__init__.py | 8 +++++-- + web_server_base/web_server_base.h | 1 + + 6 files changed, 30 insertions(+), 33 deletions(-) + +diff --git a/async_tcp/__init__.py b/async_tcp/__init__.py +index 99e250b..8ccc4d1 100644 +--- a/async_tcp/__init__.py ++++ b/async_tcp/__init__.py +@@ -6,6 +6,7 @@ from esphome.const import ( + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RTL87XX, ++ PLATFORM_RP2040 + ) + from esphome.core import CORE, coroutine_with_priority + +@@ -14,7 +15,7 @@ CODEOWNERS = ["@OttoWinter"] + CONFIG_SCHEMA = cv.All( + cv.Schema({}), + cv.only_with_arduino, +- cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), ++ cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX, PLATFORM_RP2040]), + ) + + +@@ -26,3 +27,6 @@ async def to_code(config): + elif CORE.is_esp8266: + # https://github.com/esphome/ESPAsyncTCP + cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") ++ elif CORE.is_rp2040: ++ # https://github.com/khoih-prog/AsyncTCP_RP2040W.git ++ cg.add_library("https://github.com/khoih-prog/AsyncTCP_RP2040W.git", None) +diff --git a/captive_portal/__init__.py b/captive_portal/__init__.py +index a90ea14..4583e1b 100644 +--- a/captive_portal/__init__.py ++++ b/captive_portal/__init__.py +@@ -8,6 +8,7 @@ from esphome.const import ( + PLATFORM_ESP8266, + PLATFORM_BK72XX, + PLATFORM_RTL87XX, ++ PLATFORM_RP2040 + ) + from esphome.core import coroutine_with_priority, CORE + +@@ -27,7 +28,7 @@ CONFIG_SCHEMA = cv.All( + ), + } + ).extend(cv.COMPONENT_SCHEMA), +- cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), ++ cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX, PLATFORM_RP2040]), + ) + + +@@ -47,3 +48,7 @@ async def to_code(config): + cg.add_library("DNSServer", None) + if CORE.is_libretiny: + cg.add_library("DNSServer", None) ++ if CORE.is_rp2040: ++ cg.add_library("DNSServer", None) ++ cg.add_library("WiFi", None) ++ +diff --git a/web_server/__init__.py b/web_server/__init__.py +index d846a34..71a842f 100644 +--- a/web_server/__init__.py ++++ b/web_server/__init__.py +@@ -29,6 +29,7 @@ from esphome.const import ( + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RTL87XX, ++ PLATFORM_RP2040 + ) + from esphome.core import CORE, coroutine_with_priority + import esphome.final_validate as fv +@@ -181,13 +182,14 @@ CONFIG_SCHEMA = cv.All( + esp32_idf=False, + bk72xx=True, + rtl87xx=True, ++ rp2040=True + ): cv.boolean, + cv.Optional(CONF_LOG, default=True): cv.boolean, + cv.Optional(CONF_LOCAL): cv.boolean, + cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group), + } + ).extend(cv.COMPONENT_SCHEMA), +- cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), ++ cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX, PLATFORM_RP2040]), + default_url, + validate_local, + validate_ota, +diff --git a/web_server_base/__init__.py b/web_server_base/__init__.py +index 4f89461..d54a8fc 100644 +--- a/web_server_base/__init__.py ++++ b/web_server_base/__init__.py +@@ -36,5 +36,9 @@ async def to_code(config): + cg.add_library("WiFi", None) + cg.add_library("FS", None) + cg.add_library("Update", None) +- # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json +- cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.2") ++ if CORE.is_rp2040: ++ cg.add_library("https://github.com/skilau/ESPAsyncWebServer.git", None) ++ cg.add_library("WiFi", None) ++ else: ++ # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json ++ cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.2") +diff --git a/web_server_base/web_server_base.h b/web_server_base/web_server_base.h +index f876d16..c580897 100644 +--- a/web_server_base/web_server_base.h ++++ b/web_server_base/web_server_base.h +@@ -8,6 +8,7 @@ + #include "esphome/core/component.h" + + #ifdef USE_ARDUINO ++#include + #include + #elif USE_ESP_IDF + #include "esphome/core/hal.h" +-- +2.39.5 (Apple Git-154) + diff --git a/firmware/patches/old/components.patch b/firmware/patches/old/components.patch new file mode 100644 index 0000000..2c4a999 --- /dev/null +++ b/firmware/patches/old/components.patch @@ -0,0 +1,292 @@ +diff -urN --exclude=__pycache__ components/async_tcp/__init__.py components/async_tcp/__init__.py +--- components/async_tcp/__init__.py 2025-02-11 14:27:54 ++++ components/async_tcp/__init__.py 2025-02-11 12:21:50 +@@ -6,6 +6,7 @@ + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RTL87XX, ++ PLATFORM_RP2040 + ) + from esphome.core import CORE, coroutine_with_priority + +@@ -14,7 +15,7 @@ + CONFIG_SCHEMA = cv.All( + cv.Schema({}), + cv.only_with_arduino, +- cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), ++ cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX, PLATFORM_RP2040]), + ) + + +@@ -26,3 +27,6 @@ + elif CORE.is_esp8266: + # https://github.com/esphome/ESPAsyncTCP + cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") ++ elif CORE.is_rp2040: ++ # https://github.com/khoih-prog/AsyncTCP_RP2040W.git ++ cg.add_library("khoih-prog/AsyncTCP_RP2040W", None) +diff -urN --exclude=__pycache__ components/captive_portal/__init__.py components/captive_portal/__init__.py +--- components/captive_portal/__init__.py 2025-02-11 14:27:54 ++++ components/captive_portal/__init__.py 2025-02-11 10:28:43 +@@ -8,6 +8,7 @@ + PLATFORM_ESP8266, + PLATFORM_BK72XX, + PLATFORM_RTL87XX, ++ PLATFORM_RP2040 + ) + from esphome.core import coroutine_with_priority, CORE + +@@ -27,7 +28,7 @@ + ), + } + ).extend(cv.COMPONENT_SCHEMA), +- cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), ++ cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX, PLATFORM_RP2040]), + ) + + +diff -urN --exclude=__pycache__ components/mqtt/__init__.py components/mqtt/__init__.py +--- components/mqtt/__init__.py 2025-02-11 14:27:54 ++++ components/mqtt/__init__.py 2025-02-11 14:46:30 +@@ -53,6 +53,7 @@ + PLATFORM_BK72XX, + PLATFORM_ESP32, + PLATFORM_ESP8266, ++ PLATFORM_RP2040 + ) + from esphome.core import CORE, coroutine_with_priority + +@@ -60,7 +61,7 @@ + + + def AUTO_LOAD(): +- if CORE.is_esp8266 or CORE.is_libretiny: ++ if CORE.is_esp8266 or CORE.is_libretiny or CORE.is_rp2040: + return ["async_tcp", "json"] + return ["json"] + +@@ -301,7 +302,7 @@ + } + ), + validate_config, +- cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]), ++ cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RP2040]), + ) + + +@@ -326,7 +327,8 @@ + if CORE.is_esp8266 or CORE.is_libretiny: + # https://github.com/heman/async-mqtt-client/blob/master/library.json + cg.add_library("heman/AsyncMqttClient-esphome", "2.0.0") +- ++ elif CORE.is_rp2040: ++ cg.add_library("https://github.com/skilau/async-mqtt-client.git", None) + cg.add_define("USE_MQTT") + cg.add_global(mqtt_ns.using) + +diff -urN --exclude=__pycache__ components/mqtt/mqtt_backend_rp2040.h components/mqtt/mqtt_backend_rp2040.h +--- components/mqtt/mqtt_backend_rp2040.h 1969-12-31 19:00:00 ++++ components/mqtt/mqtt_backend_rp2040.h 2025-02-11 13:16:57 +@@ -0,0 +1,103 @@ ++#pragma once ++ ++#ifdef USE_RP2040 ++ ++#include "mqtt_backend.h" ++#include ++#include ++#include "esphome/components/network/ip_address.h" ++#include "esphome/core/helpers.h" ++ ++namespace esphome { ++namespace mqtt { ++ ++class MQTTBackendRP2040 final : public MQTTBackend { ++ public: ++ void set_keep_alive(uint16_t keep_alive) final { this->keep_alive_ = keep_alive; } ++ void set_client_id(const char *client_id) final { this->client_id_ = client_id; } ++ void set_clean_session(bool clean_session) final { this->clean_session_ = clean_session; } ++ ++ void set_credentials(const char *username, const char *password) final { ++ if (username) ++ this->username_ = username; ++ if (password) ++ this->password_ = password; ++ } ++ void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final { ++ if (topic) ++ this->lwt_topic_ = topic; ++ this->lwt_qos_ = qos; ++ if (payload) ++ this->lwt_message_ = payload; ++ this->lwt_retain_ = retain; ++ } ++ void set_server(network::IPAddress ip, uint16_t port) final { ++ this->host_ = ip.str(); ++ this->port_ = port; ++ } ++ void set_server(const char *host, uint16_t port) final { ++ this->host_ = host; ++ this->port_ = port; ++ } ++ ++ void set_on_connect(std::function &&callback) final { ++ this->on_connect_.add(std::move(callback)); ++ } ++ void set_on_disconnect(std::function &&callback) final { ++ this->on_disconnect_.add(std::move(callback)); ++ } ++ void set_on_subscribe(std::function &&callback) final { ++ this->on_subscribe_.add(std::move(callback)); ++ } ++ void set_on_unsubscribe(std::function &&callback) final { ++ this->on_unsubscribe_.add(std::move(callback)); ++ } ++ void set_on_message(std::function &&callback) final { ++ this->on_message_.add(std::move(callback)); ++ } ++ void set_on_publish(std::function &&callback) final { ++ this->on_publish_.add(std::move(callback)); ++ } ++ ++ bool connected() const final { return mqtt_client_.connected(); } ++ void connect() final { mqtt_client_.connect(); } ++ void disconnect() final { mqtt_client_.disconnect(true); } ++ ++ bool subscribe(const char *topic, uint8_t qos) final { ++ return mqtt_client_.subscribe(topic, qos) != 0; ++ } ++ bool unsubscribe(const char *topic) final { ++ return mqtt_client_.unsubscribe(topic) != 0; ++ } ++ bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final { ++ return mqtt_client_.publish(topic, qos, retain, payload, length, false, 0) != 0; ++ } ++ using MQTTBackend::publish; ++ ++ protected: ++ AsyncMqttClient mqtt_client_; ++ ++ std::string host_; ++ uint16_t port_; ++ std::string username_; ++ std::string password_; ++ std::string lwt_topic_; ++ std::string lwt_message_; ++ uint8_t lwt_qos_; ++ bool lwt_retain_; ++ std::string client_id_; ++ uint16_t keep_alive_; ++ bool clean_session_; ++ ++ CallbackManager on_connect_; ++ CallbackManager on_disconnect_; ++ CallbackManager on_subscribe_; ++ CallbackManager on_unsubscribe_; ++ CallbackManager on_message_; ++ CallbackManager on_publish_; ++}; ++ ++} // namespace mqtt ++} // namespace esphome ++ ++#endif // defined(USE_RP2040) +diff -urN --exclude=__pycache__ components/mqtt/mqtt_client.cpp components/mqtt/mqtt_client.cpp +--- components/mqtt/mqtt_client.cpp 2025-02-11 14:27:54 ++++ components/mqtt/mqtt_client.cpp 2025-02-11 12:28:16 +@@ -117,6 +117,9 @@ + #ifdef USE_ESP32 + root["platform"] = "ESP32"; + #endif ++#ifdef USE_RP2040 ++ root["platform"] = "RP2040"; ++#endif + #ifdef USE_LIBRETINY + root["platform"] = lt_cpu_get_model_name(); + #endif +diff -urN --exclude=__pycache__ components/mqtt/mqtt_client.h components/mqtt/mqtt_client.h +--- components/mqtt/mqtt_client.h 2025-02-11 14:27:54 ++++ components/mqtt/mqtt_client.h 2025-02-11 12:29:29 +@@ -15,6 +15,8 @@ + #include "mqtt_backend_esp8266.h" + #elif defined(USE_LIBRETINY) + #include "mqtt_backend_libretiny.h" ++#elif defined(USE_RP2040) ++#include "mqtt_backend_rp2040.h" + #endif + #include "lwip/ip_addr.h" + +@@ -320,6 +322,8 @@ + MQTTBackendESP8266 mqtt_backend_; + #elif defined(USE_LIBRETINY) + MQTTBackendLibreTiny mqtt_backend_; ++#elif defined(USE_RP2040) ++ MQTTBackendRP2040 mqtt_backend_; + #endif + + MQTTClientState state_{MQTT_CLIENT_DISABLED}; +diff -urN --exclude=__pycache__ components/web_server/__init__.py components/web_server/__init__.py +--- components/web_server/__init__.py 2025-02-11 14:27:54 ++++ components/web_server/__init__.py 2025-02-11 10:32:54 +@@ -29,6 +29,7 @@ + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RTL87XX, ++ PLATFORM_RP2040 + ) + from esphome.core import CORE, coroutine_with_priority + import esphome.final_validate as fv +@@ -181,13 +182,14 @@ + esp32_idf=False, + bk72xx=True, + rtl87xx=True, ++ rp2040=True + ): cv.boolean, + cv.Optional(CONF_LOG, default=True): cv.boolean, + cv.Optional(CONF_LOCAL): cv.boolean, + cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group), + } + ).extend(cv.COMPONENT_SCHEMA), +- cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), ++ cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX, PLATFORM_RP2040]), + default_url, + validate_local, + validate_ota, +diff -urN --exclude=__pycache__ components/web_server/web_server.cpp components/web_server/web_server.cpp +--- components/web_server/web_server.cpp 2025-02-11 14:27:54 ++++ components/web_server/web_server.cpp 2025-02-11 14:48:23 +@@ -172,7 +172,7 @@ + + #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS + void WebServer::handle_pna_cors_request(AsyncWebServerRequest *request) { +- AsyncWebServerResponse *response = request->beginResponse(200, ""); ++ AsyncWebServerResponse *response = request->beginResponse(200, String(), String()); + response->addHeader(HEADER_CORS_ALLOW_PNA, "true"); + response->addHeader(HEADER_PNA_NAME, App.get_name().c_str()); + std::string mac = get_mac_address_pretty(); +diff -urN --exclude=__pycache__ components/web_server_base/__init__.py components/web_server_base/__init__.py +--- components/web_server_base/__init__.py 2025-02-11 14:27:54 ++++ components/web_server_base/__init__.py 2025-02-11 14:44:20 +@@ -36,5 +36,9 @@ + cg.add_library("WiFi", None) + cg.add_library("FS", None) + cg.add_library("Update", None) +- # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json +- cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.2") ++ if CORE.is_rp2040: ++ cg.add_library("https://github.com/skilau/ESPAsyncWebServer.git", None) ++ cg.add_library("WiFi", None) ++ else: ++ # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json ++ cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.2") +diff -urN --exclude=__pycache__ components/web_server_base/web_server_base.h components/web_server_base/web_server_base.h +--- components/web_server_base/web_server_base.h 2025-02-11 14:27:54 ++++ components/web_server_base/web_server_base.h 2025-02-11 12:36:10 +@@ -8,6 +8,7 @@ + #include "esphome/core/component.h" + + #ifdef USE_ARDUINO ++#include + #include + #elif USE_ESP_IDF + #include "esphome/core/hal.h" diff --git a/firmware/patches/old/openspool.patch b/firmware/patches/old/openspool.patch new file mode 100644 index 0000000..1c99a28 --- /dev/null +++ b/firmware/patches/old/openspool.patch @@ -0,0 +1,302 @@ +diff --git a/firmware/Makefile b/firmware/Makefile +index b45fa23..d91a013 100644 +--- a/firmware/Makefile ++++ b/firmware/Makefile +@@ -17,6 +17,7 @@ clean: + esphome clean lolin_s2_mini.yaml + esphome clean lolin_s3_mini.yaml + esphome clean esp32-s3-devkitc-1.yaml ++ esphome clean rpipicow.yaml + rm -rf .esphome/idedata/* + rm -rf ~/.platformio/penv + # build-esp32s2-mini: +@@ -69,6 +70,9 @@ lolin_s2_mini: + lolin_s3_mini: + esphome \ + run lolin_s3_mini.yaml --device $(USB_ADDRESS) ++rpipicow: ++ esphome \ ++ run rpipicow.yaml --device $(USB_ADDRESS) + devkit: + esphome \ +- run esp32-s3-devkitc-1.yaml --device $(USB_ADDRESS) +\ No newline at end of file ++ run esp32-s3-devkitc-1.yaml --device $(USB_ADDRESS) +diff --git a/firmware/common.yaml b/firmware/common.yaml +index abf9f48..a7a8db5 100644 +--- a/firmware/common.yaml ++++ b/firmware/common.yaml +@@ -12,39 +12,39 @@ esphome: + build_unflags: -std=gnu++11 + build_flags: + - -std=gnu++14 +- - -DMBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\" ++ #- -DMBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\" + on_boot: + then: + #TODO: breahting blue studders a little bit +- - light.turn_on: +- id: neopixel_light +- effect: Breathing Blue +- brightness: 100% ++ # - light.turn_on: ++ # id: neopixel_light ++ # effect: Breathing Blue ++ # brightness: 100% + - wait_until: + condition: + wifi.connected: + - delay: 100ms +- - light.turn_on: +- id: neopixel_light +- effect: none +- - delay: 100ms +- - light.turn_on: +- id: neopixel_light +- effect: Rainbow +- - delay: 4s +- - light.turn_on: +- id: neopixel_light +- effect: none +- brightness: 50% ++ # - light.turn_on: ++ # id: neopixel_light ++ # effect: none + # - delay: 100ms +- # - light.addressable_set: ++ # - light.turn_on: + # id: neopixel_light +- # color_brightness: 50% +- # range_from: 0 +- # range_to: ${led_count} +- # red: 50% +- # green: 50% +- # blue: 50% ++ # effect: Rainbow ++ # - delay: 4s ++ # - light.turn_on: ++ # id: neopixel_light ++ # effect: none ++ # brightness: 50% ++ # # - delay: 100ms ++ # - light.addressable_set: ++ # id: neopixel_light ++ # color_brightness: 50% ++ # range_from: 0 ++ # range_to: ${led_count} ++ # red: 50% ++ # green: 50% ++ # blue: 50% + - if: + condition: + lambda: |- +@@ -72,17 +72,17 @@ esphome: + # } + # - lambda: |- + # id(my_ota).set_auth_password("New password"); +- on_shutdown: +- then: +- - script.execute: set_led_off +-esp32: +- framework: +- type: esp-idf +- version: 5.3.1 +- platform_version: 6.9.0 # https://github.com/platformio/platform-espressif32/releases/ +- sdkconfig_options: +- CONFIG_MBEDTLS_HKDF_C: y # Needed for bambu KDF +- CONFIG_MBEDTLS_MD_C: y # Needed for bambu KDF ++ # on_shutdown: ++ # then: ++ # - script.execute: set_led_off ++#esp32: ++# framework: ++# type: esp-idf ++# version: 5.3.1 ++# platform_version: 6.9.0 # https://github.com/platformio/platform-espressif32/releases/ ++# sdkconfig_options: ++# CONFIG_MBEDTLS_HKDF_C: y # Needed for bambu KDF ++# CONFIG_MBEDTLS_MD_C: y # Needed for bambu KDF + # version: recommended + # sdkconfig_options: + # MBEDTLS_CERTIFICATE_BUNDLE: y +@@ -107,7 +107,7 @@ packages: + bambu_printer: !include conf.d/bambu_printer.yaml + pn532_rfid-solo: !include conf.d/pn532_rfid-solo.yaml + automation: !include conf.d/automation.yaml +- led-external: !include conf.d/led-external.yaml ++ #led-external: !include conf.d/led-external.yaml + ota: !include conf.d/ota.yaml + #update: !include conf.d/update.yaml + api: !include conf.d/api.yaml +diff --git a/firmware/conf.d/button.yaml b/firmware/conf.d/button.yaml +index 048e5ae..b5f195c 100644 +--- a/firmware/conf.d/button.yaml ++++ b/firmware/conf.d/button.yaml +@@ -10,19 +10,19 @@ binary_sensor: + mode: INPUT_PULLUP + inverted: true + on_press: +- then: +- - script.execute: +- id: set_led_red +- led_number: -1 ++ # then: ++ # - script.execute: ++ # id: set_led_red ++ # led_number: -1 + - globals.set: + id: button_press_duration + value: "0" + - script.execute: start_timer + on_release: + then: +- - script.execute: +- id: set_led_white +- led_number: -1 ++ # - script.execute: ++ # id: set_led_white ++ # led_number: -1 + - script.stop: timer_script + - if: + condition: +diff --git a/firmware/conf.d/logger.yaml b/firmware/conf.d/logger.yaml +index 27c1ca8..aba9ce0 100644 +--- a/firmware/conf.d/logger.yaml ++++ b/firmware/conf.d/logger.yaml +@@ -10,10 +10,11 @@ logger: + mqtt: DEBUG + mqtt.component: DEBUG + mqtt.client: DEBUG +- wifi: INFO ++ #wifi: INFO ++ wifi: WARN + wifi_esp32: INFO + esp-tls: DEBUG +- esp-tls-mbedtls: DEBUG ++ #esp-tls-mbedtls: DEBUG + http_client: INFO + http_request: INFO + scheduler: INFO +diff --git a/firmware/conf.d/mqtt_bambu_lan.yaml b/firmware/conf.d/mqtt_bambu_lan.yaml +index 540d5f3..4709495 100644 +--- a/firmware/conf.d/mqtt_bambu_lan.yaml ++++ b/firmware/conf.d/mqtt_bambu_lan.yaml +@@ -24,29 +24,29 @@ mqtt: + # Dont post update to MQTT + # keepalive: 15s + # idf_send_async: true +- skip_cert_cn_check: true +- certificate_authority: | +- -----BEGIN CERTIFICATE----- +- MIIDZTCCAk2gAwIBAgIUV1FckwXElyek1onFnQ9kL7Bk4N8wDQYJKoZIhvcNAQEL +- BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u +- LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yMjA0MDQwMzQyMTFaFw0zMjA0MDEw +- MzQyMTFaMEIxCzAJBgNVBAYTAkNOMSIwIAYDVQQKDBlCQkwgVGVjaG5vbG9naWVz +- IENvLiwgTHRkMQ8wDQYDVQQDDAZCQkwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +- DwAwggEKAoIBAQDL3pnDdxGOk5Z6vugiT4dpM0ju+3Xatxz09UY7mbj4tkIdby4H +- oeEdiYSZjc5LJngJuCHwtEbBJt1BriRdSVrF6M9D2UaBDyamEo0dxwSaVxZiDVWC +- eeCPdELpFZdEhSNTaT4O7zgvcnFsfHMa/0vMAkvE7i0qp3mjEzYLfz60axcDoJLk +- p7n6xKXI+cJbA4IlToFjpSldPmC+ynOo7YAOsXt7AYKY6Glz0BwUVzSJxU+/+VFy +- /QrmYGNwlrQtdREHeRi0SNK32x1+bOndfJP0sojuIrDjKsdCLye5CSZIvqnbowwW +- 1jRwZgTBR29Zp2nzCoxJYcU9TSQp/4KZuWNVAgMBAAGjUzBRMB0GA1UdDgQWBBSP +- NEJo3GdOj8QinsV8SeWr3US+HjAfBgNVHSMEGDAWgBSPNEJo3GdOj8QinsV8SeWr +- 3US+HjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQABlBIT5ZeG +- fgcK1LOh1CN9sTzxMCLbtTPFF1NGGA13mApu6j1h5YELbSKcUqfXzMnVeAb06Htu +- 3CoCoe+wj7LONTFO++vBm2/if6Jt/DUw1CAEcNyqeh6ES0NX8LJRVSe0qdTxPJuA +- BdOoo96iX89rRPoxeed1cpq5hZwbeka3+CJGV76itWp35Up5rmmUqrlyQOr/Wax6 +- itosIzG0MfhgUzU51A2P/hSnD3NDMXv+wUY/AvqgIL7u7fbDKnku1GzEKIkfH8hm +- Rs6d8SCU89xyrwzQ0PR853irHas3WrHVqab3P+qNwR0YirL0Qk7Xt/q3O1griNg2 +- Blbjg3obpHo9 +- -----END CERTIFICATE----- ++ # skip_cert_cn_check: true ++ # certificate_authority: | ++ # -----BEGIN CERTIFICATE----- ++ # MIIDZTCCAk2gAwIBAgIUV1FckwXElyek1onFnQ9kL7Bk4N8wDQYJKoZIhvcNAQEL ++ # BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u ++ # LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yMjA0MDQwMzQyMTFaFw0zMjA0MDEw ++ # MzQyMTFaMEIxCzAJBgNVBAYTAkNOMSIwIAYDVQQKDBlCQkwgVGVjaG5vbG9naWVz ++ # IENvLiwgTHRkMQ8wDQYDVQQDDAZCQkwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB ++ # DwAwggEKAoIBAQDL3pnDdxGOk5Z6vugiT4dpM0ju+3Xatxz09UY7mbj4tkIdby4H ++ # oeEdiYSZjc5LJngJuCHwtEbBJt1BriRdSVrF6M9D2UaBDyamEo0dxwSaVxZiDVWC ++ # eeCPdELpFZdEhSNTaT4O7zgvcnFsfHMa/0vMAkvE7i0qp3mjEzYLfz60axcDoJLk ++ # p7n6xKXI+cJbA4IlToFjpSldPmC+ynOo7YAOsXt7AYKY6Glz0BwUVzSJxU+/+VFy ++ # /QrmYGNwlrQtdREHeRi0SNK32x1+bOndfJP0sojuIrDjKsdCLye5CSZIvqnbowwW ++ # 1jRwZgTBR29Zp2nzCoxJYcU9TSQp/4KZuWNVAgMBAAGjUzBRMB0GA1UdDgQWBBSP ++ # NEJo3GdOj8QinsV8SeWr3US+HjAfBgNVHSMEGDAWgBSPNEJo3GdOj8QinsV8SeWr ++ # 3US+HjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQABlBIT5ZeG ++ # fgcK1LOh1CN9sTzxMCLbtTPFF1NGGA13mApu6j1h5YELbSKcUqfXzMnVeAb06Htu ++ # 3CoCoe+wj7LONTFO++vBm2/if6Jt/DUw1CAEcNyqeh6ES0NX8LJRVSe0qdTxPJuA ++ # BdOoo96iX89rRPoxeed1cpq5hZwbeka3+CJGV76itWp35Up5rmmUqrlyQOr/Wax6 ++ # itosIzG0MfhgUzU51A2P/hSnD3NDMXv+wUY/AvqgIL7u7fbDKnku1GzEKIkfH8hm ++ # Rs6d8SCU89xyrwzQ0PR853irHas3WrHVqab3P+qNwR0YirL0Qk7Xt/q3O1griNg2 ++ # Blbjg3obpHo9 ++ # -----END CERTIFICATE----- + clean_session: true + enable_on_boot: false + on_connect: +diff --git a/firmware/conf.d/pn532_rfid-solo.yaml b/firmware/conf.d/pn532_rfid-solo.yaml +index 4a11a4a..f527749 100644 +--- a/firmware/conf.d/pn532_rfid-solo.yaml ++++ b/firmware/conf.d/pn532_rfid-solo.yaml +@@ -30,9 +30,9 @@ pn532_spi: + update_interval: ${update_interval} + on_tag_removed: + then: +- - script.execute: +- id: set_led_white +- led_number: 0 ++ # - script.execute: ++ # id: set_led_white ++ # led_number: 0 + - binary_sensor.template.publish: + id: nfc_tag_present0 + state: OFF +@@ -90,14 +90,14 @@ pn532_spi: + id(rfid_reader_spi_0_tag_is_openspool).publish_state(is_valid_openspool); + + // Set LED color +- if (is_valid_openspool) { ++ /*if (is_valid_openspool) { + id(set_led_green).execute(0); + //TODO: Generate and send MQTT message here + if (!payload.empty()) { + } + } else { + id(set_led_red).execute(0); +- } ++ }*/ + - if: + condition: + and: +diff --git a/firmware/conf.d/status_led.yaml b/firmware/conf.d/status_led.yaml +index 3c575fe..37c2a5c 100644 +--- a/firmware/conf.d/status_led.yaml ++++ b/firmware/conf.d/status_led.yaml +@@ -2,5 +2,5 @@ + output: + - id: onboard_led + platform: gpio +- pin: 15 ++ pin: 0 + inverted: false +diff --git a/firmware/conf.d/wifi.yaml b/firmware/conf.d/wifi.yaml +index 1a9ccf1..b7e9c24 100644 +--- a/firmware/conf.d/wifi.yaml ++++ b/firmware/conf.d/wifi.yaml +@@ -1,7 +1,14 @@ + --- + wifi: +- ap: +- ssid: "OpenSpool" ++# ap: ++# ssid: "OpenSpool" ++# password: "OpenSpool" ++ ssid: "" ++ password: "" ++ manual_ip: ++ static_ip: ++ gateway: ++ subnet: + reboot_timeout: 0s # This must stay at 0s if api and mqtt are enabled, otherwise it will reboot every 15 minutes + #TODO: if it is in ap mode, then pulse blue + # on_connect: diff --git a/firmware/rpipicow.yaml b/firmware/rpipicow.yaml new file mode 100644 index 0000000..ebbdeab --- /dev/null +++ b/firmware/rpipicow.yaml @@ -0,0 +1,70 @@ +esphome: + name: rpipicow + libraries: + - https://github.com/skilau/ESPAsyncWebServer.git + platformio_options: + platform_packages: + - framework-arduinopico @ https://github.com/earlephilhower/arduino-pico.git + board_build.filesystem_size: 0.5m + on_boot: + then: + - component.suspend: tap_timer # Remove this if only using the spool holder; it's for AMS-external only + +rp2040: + board: rpipicow + framework: + platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git#develop + +substitutions: + hide_ams_sensors: 'true' + led_pin: GPIO15 + spi2_type: spi + spi2_clk_pin: GPIO2 + spi2_miso_pin: GPIO4 + spi2_mosi_pin: GPIO3 + rfid0_spi_interface: SPI2 + rfid0_ss_pin: GPIO1 + #neopixel_pin: '28' + mqtt: "rp2040_mqtt" + +external_components: + - source: + type: local + path: components + components: [ rp2040_mqtt ] + +rp2040_mqtt: + id: bambu_mqtt + secure: true + mqtt_host: "" + mqtt_port: 8883 + auth: true + mqtt_user: bblp + mqtt_pass: "" + +# I don't know why this part seems to be necessary for the device to retain the wifi credentials, but it does. +globals: + - id: global_wifi_ssid + type: std::string + restore_value: yes + - id: global_wifi_password + type: std::string + restore_value: yes + +mqtt: !remove + +packages: + improv-serial: !include conf.d/improv-serial.yaml + state-topic: !include conf.d/state_topic.yaml + openspool-mini: !include openspool-mini.yaml + led-external: !include conf.d/led-external_rp2040.yaml + extra: !include conf.d/extra.yaml + button: !include conf.d/button.yaml + +button: + - platform: factory_reset + name: Restart with Factory Default Settings + + + +