diff --git a/.cursor/commands/openspec-apply.md b/.cursor/commands/openspec-apply.md new file mode 100644 index 0000000..188bc1e --- /dev/null +++ b/.cursor/commands/openspec-apply.md @@ -0,0 +1,23 @@ +--- +name: /openspec-apply +id: openspec-apply +category: OpenSpec +description: 实施已批准的 OpenSpec 变更并保持任务同步。 +--- + +**Guardrails** +- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. +- Keep changes tightly scoped to the requested outcome. +- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. + +**Steps** +Track these steps as TODOs and complete them one by one. +1. Read `changes//proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria. +2. Work through tasks sequentially, keeping edits minimal and focused on the requested change. +3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished. +4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality. +5. Reference `openspec list` or `openspec show ` when additional context is required. + +**Reference** +- Use `openspec show --json --deltas-only` if you need additional context from the proposal while implementing. + diff --git a/.cursor/commands/openspec-archive.md b/.cursor/commands/openspec-archive.md new file mode 100644 index 0000000..d68e807 --- /dev/null +++ b/.cursor/commands/openspec-archive.md @@ -0,0 +1,27 @@ +--- +name: /openspec-archive +id: openspec-archive +category: OpenSpec +description: 归档已部署的 OpenSpec 变更并更新规范。 +--- + +**Guardrails** +- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. +- Keep changes tightly scoped to the requested outcome. +- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. + +**Steps** +1. Determine the change ID to archive: + - If this prompt already includes a specific change ID (for example inside a `` block populated by slash-command arguments), use that value after trimming whitespace. + - If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends. + - Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding. + - If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet. +2. Validate the change ID by running `openspec list` (or `openspec show `) and stop if the change is missing, already archived, or otherwise not ready to archive. +3. Run `openspec archive --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work). +4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`. +5. Validate with `openspec validate --strict` and inspect with `openspec show ` if anything looks off. + +**Reference** +- Use `openspec list` to confirm change IDs before archiving. +- Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off. + diff --git a/.cursor/commands/openspec-proposal.md b/.cursor/commands/openspec-proposal.md new file mode 100644 index 0000000..4240b50 --- /dev/null +++ b/.cursor/commands/openspec-proposal.md @@ -0,0 +1,27 @@ +--- +name: /openspec-proposal +id: openspec-proposal +category: OpenSpec +description: 搭建新的 OpenSpec 变更并进行严格验证。 +--- + +**Guardrails** +- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. +- Keep changes tightly scoped to the requested outcome. +- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. +- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files. + +**Steps** +1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification. +2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes//`. +3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing. +4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs. +5. Draft spec deltas in `changes//specs//spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant. +6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work. +7. Validate with `openspec validate --strict` and resolve every issue before sharing the proposal. + +**Reference** +- Use `openspec show --json --deltas-only` or `openspec show --type spec` to inspect details when validation fails. +- Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones. +- Explore the codebase with `rg `, `ls`, or direct file reads so proposals align with current implementation realities. + diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index dafb8ad..27d1459 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -ARG DOCKER_TAG=latest +ARG DOCKER_TAG=v5.3.2 FROM espressif/idf:${DOCKER_TAG} ENV LC_ALL=C.UTF-8 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..08acc55 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,82 @@ +name: Build and Release + +on: + push: + tags: + - 'v*' # 匹配 v 开头的标签,如 v1.0.0 + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get tag name + id: tag + run: echo "name=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + + - name: Build MOTORS_4 version + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v5.3.2 + target: esp32c3 + command: | + idf.py build -DMOTOR_CONFIG=MOTORS_4 && \ + python -m esptool --chip esp32c3 merge_bin -o Top-AMS-MOTORS_4.bin \ + --flash_mode dio --flash_freq 80m --flash_size 4MB \ + 0x0 build/bootloader/bootloader.bin \ + 0x10000 build/Top-AMS.bin \ + 0x8000 build/partition_table/partition-table.bin && \ + rm -rf build + + - name: Build MOTORS_6 version + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v5.3.2 + target: esp32c3 + command: | + idf.py build -DMOTOR_CONFIG=MOTORS_6 && \ + python -m esptool --chip esp32c3 merge_bin -o Top-AMS-MOTORS_6.bin \ + --flash_mode dio --flash_freq 80m --flash_size 4MB \ + 0x0 build/bootloader/bootloader.bin \ + 0x10000 build/Top-AMS.bin \ + 0x8000 build/partition_table/partition-table.bin && \ + rm -rf build + + - name: Build MOTORS_8 version + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v5.3.2 + target: esp32c3 + command: | + idf.py build -DMOTOR_CONFIG=MOTORS_8 && \ + python -m esptool --chip esp32c3 merge_bin -o Top-AMS-MOTORS_8.bin \ + --flash_mode dio --flash_freq 80m --flash_size 4MB \ + 0x0 build/bootloader/bootloader.bin \ + 0x10000 build/Top-AMS.bin \ + 0x8000 build/partition_table/partition-table.bin + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.tag.outputs.name }} + name: Release ${{ steps.tag.outputs.name }} + body: | + ## 版本说明 + + 本次发布包含以下三个版本的固件: + - **MOTORS_4**: 4通道版本 + - **MOTORS_6**: 6通道版本 + - **MOTORS_8**: 8通道版本(其它) + + ## 使用说明 + + 请根据您的硬件配置选择对应的固件进行刷入。 + files: | + Top-AMS-MOTORS_4.bin + Top-AMS-MOTORS_6.bin + Top-AMS-MOTORS_8.bin + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index e4c961a..eb3b7c6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ managed_components/ localconfig.hpp temp/ main/index.hpp -Top-AMS.bin \ No newline at end of file +Top-AMS.bin +.DS_Store \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0669699 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,18 @@ + +# OpenSpec Instructions + +These instructions are for AI assistants working in this project. + +Always open `@/openspec/AGENTS.md` when the request: +- Mentions planning or proposals (words like proposal, spec, change, plan) +- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work +- Sounds ambiguous and you need the authoritative spec before coding + +Use `@/openspec/AGENTS.md` to learn: +- How to create and apply change proposals +- Spec format and conventions +- Project structure and guidelines + +Keep this managed block so 'openspec update' can refresh the instructions. + + \ No newline at end of file diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 0000000..2fec228 --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,51 @@ +# Release Notes + +## 版本信息 +- **版本号**: v{{VERSION}} +- **发布日期**: {{DATE}} +- **固件版本**: {{FIRMWARE_VERSION}} + +## 更新内容 + +### 🎉 新功能 +- + +### 🐛 问题修复 +- + +### ⚡ 性能优化 +- + +### 📝 文档更新 +- + +### 🔧 配置变更 +- + +## 升级说明 + +### 重要提示 +- + +### 兼容性 +- 支持的打印机型号: A1 mini, A1, P1S +- 支持的固件版本: 1.04.00及以上 + +### 升级步骤 +1. 下载最新固件文件 +2. 使用 ESP-IDF 工具刷入固件 +3. 重启设备 +4. 检查 Web 界面是否正常 + +## 下载 +- 固件文件: [下载链接]({{RELEASE_URL}}) + +## 反馈 +如有问题或建议,请通过以下方式反馈: +- GitHub Issues +- QQ群: 8820913⑨九 + + + + + diff --git "a/doc/A1mini\345\220\257\345\212\250_\345\216\237\347\211\210.gcode" "b/doc/A1mini\345\220\257\345\212\250_\345\216\237\347\211\210.gcode" new file mode 100644 index 0000000..240e158 --- /dev/null +++ "b/doc/A1mini\345\220\257\345\212\250_\345\216\237\347\211\210.gcode" @@ -0,0 +1,459 @@ +;===== machine: A1 mini ========================= +;===== date: 20250822 ================== + +;===== start to heat heatbead&hotend========== +M1002 gcode_claim_action : 2 +M1002 set_filament_type:{filament_type[initial_no_support_extruder]} +M104 S170 +M140 S[bed_temperature_initial_layer_single] +G392 S0 ;turn off clog detect +M9833.2 +;=====start printer sound =================== +M17 +M400 S1 +M1006 S1 +M1006 A0 B0 L100 C37 D10 M100 E37 F10 N100 +M1006 A0 B0 L100 C41 D10 M100 E41 F10 N100 +M1006 A0 B0 L100 C44 D10 M100 E44 F10 N100 +M1006 A0 B10 L100 C0 D10 M100 E0 F10 N100 +M1006 A43 B10 L100 C39 D10 M100 E46 F10 N100 +M1006 A0 B0 L100 C0 D10 M100 E0 F10 N100 +M1006 A0 B0 L100 C39 D10 M100 E43 F10 N100 +M1006 A0 B0 L100 C0 D10 M100 E0 F10 N100 +M1006 A0 B0 L100 C41 D10 M100 E41 F10 N100 +M1006 A0 B0 L100 C44 D10 M100 E44 F10 N100 +M1006 A0 B0 L100 C49 D10 M100 E49 F10 N100 +M1006 A0 B0 L100 C0 D10 M100 E0 F10 N100 +M1006 A44 B10 L100 C39 D10 M100 E48 F10 N100 +M1006 A0 B0 L100 C0 D10 M100 E0 F10 N100 +M1006 A0 B0 L100 C39 D10 M100 E44 F10 N100 +M1006 A0 B0 L100 C0 D10 M100 E0 F10 N100 +M1006 A43 B10 L100 C39 D10 M100 E46 F10 N100 +M1006 W +M18 +;=====avoid end stop ================= +G91 +G380 S2 Z30 F1200 +G380 S3 Z-20 F1200 +G1 Z5 F1200 +G90 + +;===== reset machine status ================= +M204 S6000 + +M630 S0 P0 +G91 +M17 Z0.3 ; lower the z-motor current + +G90 +M17 X0.7 Y0.9 Z0.5 ; reset motor current to default +M960 S5 P1 ; turn on logo lamp +G90 +M83 +M220 S100 ;Reset Feedrate +M221 S100 ;Reset Flowrate +M73.2 R1.0 ;Reset left time magnitude +;====== cog noise reduction================= +M982.2 S1 ; turn on cog noise reduction + +;===== prepare print temperature and material ========== +M400 +M18 +M109 S100 H170 +M104 S170 +M400 +M17 +M400 +G28 X + +M211 X0 Y0 Z0 ;turn off soft endstop ; turn off soft endstop to prevent protential logic problem + +M975 S1 ; turn on + +G1 X0.0 F30000 +G1 X-13.5 F3000 + +M620 M ;enable remap +M620 S[initial_no_support_extruder]A ; switch material if AMS exist + G392 S0 ;turn on clog detect + M1002 gcode_claim_action : 4 + M400 + M1002 set_filament_type:UNKNOWN + M109 S[nozzle_temperature_initial_layer] + M104 S250 + M400 + T[initial_no_support_extruder] + G1 X-13.5 F3000 + M400 + M620.1 E F{flush_volumetric_speeds[initial_no_support_extruder]/2.4053*60} T{flush_temperatures[initial_no_support_extruder]} + M109 S250 ;set nozzle to common flush temp + M106 P1 S0 + G92 E0 + G1 E50 F200 + M400 + M1002 set_filament_type:{filament_type[initial_no_support_extruder]} + M104 S{flush_temperatures[initial_no_support_extruder]} + G92 E0 + G1 E50 F{flush_volumetric_speeds[initial_no_support_extruder]/2.4053*60} + M400 + M106 P1 S178 + G92 E0 + G1 E5 F{flush_volumetric_speeds[initial_no_support_extruder]/2.4053*60} + M109 S{nozzle_temperature_initial_layer[initial_no_support_extruder]-20} ; drop nozzle temp, make filament shink a bit + M104 S{nozzle_temperature_initial_layer[initial_no_support_extruder]-40} + G92 E0 + G1 E-0.5 F300 + + G1 X0 F30000 + G1 X-13.5 F3000 + G1 X0 F30000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + G1 X0 F30000 + G1 X-13.5 F3000 + M109 S{nozzle_temperature_initial_layer[initial_no_support_extruder]-40} + G392 S0 ;turn off clog detect +M621 S[initial_no_support_extruder]A + +M400 +M106 P1 S0 +;===== prepare print temperature and material end ===== + + +;===== mech mode fast check============================ +M1002 gcode_claim_action : 3 +G0 X25 Y175 F20000 ; find a soft place to home +;M104 S0 +G28 Z P0 T300; home z with low precision,permit 300deg temperature +G29.2 S0 ; turn off ABL +M104 S170 + +; build plate detect +M1002 judge_flag build_plate_detect_flag +M622 S1 + G39.4 + M400 +M623 + +G1 Z5 F3000 +G1 X90 Y-1 F30000 +M400 P200 +M970.3 Q1 A7 K0 O2 +M974 Q1 S2 P0 + +G1 X90 Y0 Z5 F30000 +M400 P200 +M970 Q0 A10 B50 C90 H15 K0 M20 O3 +M974 Q0 S2 P0 + +M975 S1 +G1 F30000 +G1 X-1 Y10 +G28 X ; re-home XY + +;===== wipe nozzle =============================== +M1002 gcode_claim_action : 14 +M975 S1 + +M104 S170 ; set temp down to heatbed acceptable +M106 S255 ; turn on fan (G28 has turn off fan) +M211 S; push soft endstop status +M211 X0 Y0 Z0 ;turn off Z axis endstop + +M83 +G1 E-1 F500 +G90 +M83 + +M109 S170 +M104 S140 +G0 X90 Y-4 F30000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X91 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X92 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X93 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X94 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X95 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X96 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X97 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X98 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X99 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X99 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X99 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X99 F10000 +G380 S3 Z-5 F1200 +G1 Z2 F1200 +G1 X99 F10000 +G380 S3 Z-5 F1200 + +G1 Z5 F30000 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +G1 X25 Y175 F30000.1 ;Brush material +G1 Z0.2 F30000.1 +G1 Y185 +G91 +G1 X-30 F30000 +G1 Y-2 +G1 X27 +G1 Y1.5 +G1 X-28 +G1 Y-2 +G1 X30 +G1 Y1.5 +G1 X-30 +G90 +M83 + +G1 Z5 F3000 +G0 X50 Y175 F20000 ; find a soft place to home +G28 Z P0 T300; home z with low precision, permit 300deg temperature +G29.2 S0 ; turn off ABL + +G0 X85 Y185 F10000 ;move to exposed steel surface and stop the nozzle +G0 Z-1.01 F10000 +G91 + +G2 I1 J0 X2 Y0 F2000.1 +G2 I-0.75 J0 X-1.5 +G2 I1 J0 X2 +G2 I-0.75 J0 X-1.5 +G2 I1 J0 X2 +G2 I-0.75 J0 X-1.5 +G2 I1 J0 X2 +G2 I-0.75 J0 X-1.5 +G2 I1 J0 X2 +G2 I-0.75 J0 X-1.5 +G2 I1 J0 X2 +G2 I-0.75 J0 X-1.5 +G2 I1 J0 X2 +G2 I-0.75 J0 X-1.5 +G2 I1 J0 X2 +G2 I-0.75 J0 X-1.5 +G2 I1 J0 X2 +G2 I-0.75 J0 X-1.5 +G2 I1 J0 X2 +G2 I-0.75 J0 X-1.5 + +G90 +G1 Z5 F30000 +G1 X25 Y175 F30000.1 ;Brush material +G1 Z0.2 F30000.1 +G1 Y185 +G91 +G1 X-30 F30000 +G1 Y-2 +G1 X27 +G1 Y1.5 +G1 X-28 +G1 Y-2 +G1 X30 +G1 Y1.5 +G1 X-30 +G90 +M83 + +G1 Z5 +G0 X55 Y175 F20000 ; find a soft place to home +G28 Z P0 T300; home z with low precision, permit 300deg temperature +G29.2 S0 ; turn off ABL + +G1 Z10 +G1 X85 Y185 +G1 Z-1.01 +G1 X95 +G1 X90 + +M211 R; pop softend status + +M106 S0 ; turn off fan , too noisy +;===== wipe nozzle end ================================ + + +;===== wait heatbed ==================== +M1002 gcode_claim_action : 2 +M104 S0 +M190 S[bed_temperature_initial_layer_single];set bed temp +M109 S140 + +G1 Z5 F3000 +G29.2 S1 +G1 X10 Y10 F20000 + +;===== bed leveling ================================== +;M1002 set_flag g29_before_print_flag=1 +M1002 judge_flag g29_before_print_flag +M622 J1 + M1002 gcode_claim_action : 1 + G29 A1 X{first_layer_print_min[0]} Y{first_layer_print_min[1]} I{first_layer_print_size[0]} J{first_layer_print_size[1]} + M400 + M500 ; save cali data +M623 +;===== bed leveling end ================================ + +;===== home after wipe mouth============================ +M1002 judge_flag g29_before_print_flag +M622 J0 + + M1002 gcode_claim_action : 13 + G28 T145 + +M623 + +;===== home after wipe mouth end ======================= + +M975 S1 ; turn on vibration supression +;===== nozzle load line =============================== +M975 S1 +G90 +M83 +T1000 + +G1 X-13.5 Y0 Z10 F10000 +G1 E1.2 F500 +M400 +M1002 set_filament_type:UNKNOWN +M109 S{nozzle_temperature[initial_extruder]} +M400 + +M412 S1 ; ===turn on filament runout detection=== +M400 P10 + +G392 S0 ;turn on clog detect + +M620.3 W1; === turn on filament tangle detection=== +M400 S2 + +M1002 set_filament_type:{filament_type[initial_no_support_extruder]} +;M1002 set_flag extrude_cali_flag=1 +M1002 judge_flag extrude_cali_flag +M622 J1 + M1002 gcode_claim_action : 8 + + M400 + M900 K0.0 L1000.0 M1.0 + G90 + M83 + G0 X68 Y-4 F30000 + G0 Z0.3 F18000 ;Move to start position + M400 + G0 X88 E10 F{outer_wall_volumetric_speed/(24/20) * 60} + G0 X93 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60} + G0 X98 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60} + G0 X103 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60} + G0 X108 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60} + G0 X113 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60} + G0 Y0 Z0 F20000 + M400 + + G1 X-13.5 Y0 Z10 F10000 + M400 + + G1 E10 F{outer_wall_volumetric_speed/2.4*60} + M983 F{outer_wall_volumetric_speed/2.4} A0.3 H[nozzle_diameter]; cali dynamic extrusion compensation + M106 P1 S178 + M400 S7 + G1 X0 F18000 + G1 X-13.5 F3000 + G1 X0 F18000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + G1 X-13.5 F3000 + M400 + M106 P1 S0 + + M1002 judge_last_extrude_cali_success + M622 J0 + M983 F{outer_wall_volumetric_speed/2.4} A0.3 H[nozzle_diameter]; cali dynamic extrusion compensation + M106 P1 S178 + M400 S7 + G1 X0 F18000 + G1 X-13.5 F3000 + G1 X0 F18000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + M400 + M106 P1 S0 + M623 + + G1 X-13.5 F3000 + M400 + M984 A0.1 E1 S1 F{outer_wall_volumetric_speed/2.4} H[nozzle_diameter] + M106 P1 S178 + M400 S7 + G1 X0 F18000 + G1 X-13.5 F3000 + G1 X0 F18000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + G1 X-13.5 F3000 + M400 + M106 P1 S0 + +M623 ; end of "draw extrinsic para cali paint" + +;===== extrude cali test =============================== +M104 S{nozzle_temperature_initial_layer[initial_extruder]} +G90 +M83 +G0 X68 Y-2.5 F30000 +G0 Z0.3 F18000 ;Move to start position +G0 X88 E10 F{outer_wall_volumetric_speed/(24/20) * 60} +G0 X93 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60} +G0 X98 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60} +G0 X103 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60} +G0 X108 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60} +G0 X113 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60} +G0 X115 Z0 F20000 +G0 Z5 +M400 + +;========turn off light and wait extrude temperature ============= +M1002 gcode_claim_action : 0 + +M400 ; wait all motion done before implement the emprical L parameters + +;===== for Textured PEI Plate , lower the nozzle as the nozzle was touching topmost of the texture when homing == +;curr_bed_type={curr_bed_type} +{if curr_bed_type=="Textured PEI Plate"} +G29.1 Z{-0.02} ; for Textured PEI Plate +{endif} + +M960 S1 P0 ; turn off laser +M960 S2 P0 ; turn off laser +M106 S0 ; turn off fan +M106 P2 S0 ; turn off big fan +M106 P3 S0 ; turn off chamber fan + +M975 S1 ; turn on mech mode supression +G90 +M83 +T1000 + +M211 X0 Y0 Z0 ;turn off soft endstop +M1007 S1 + + + diff --git "a/doc/A1mini\345\277\253\351\200\237\345\220\257\345\212\250.gcode" "b/doc/A1mini\345\277\253\351\200\237\345\220\257\345\212\250.gcode" new file mode 100644 index 0000000..ddda42b --- /dev/null +++ "b/doc/A1mini\345\277\253\351\200\237\345\220\257\345\212\250.gcode" @@ -0,0 +1,472 @@ +; Optimized start routine: Provides a single drop-in script +; for an optimized startup routine. Faster, safer, & easier +;===== machine: A1 Mini =================================== +;===== date: 20251111 ===================================== +;===== version: 1.3.4 ===================================== +;===== modified by: Cascade Media LLC ===================== +;===== changelog: ========================================= +;===== 1.3.4 - Disabled vibration compensation due to eddy sensor warnings +;===== 1.3.3 - Enabled quick vibration compensation +;===== 1.3.2 - Disabled vibration compensation +; - Shows signs of underextrusion when enabled +;===== 1.3.1 - Adds quick version of vibration compensation +;===== 1.3 - Adds 0.2mm and 0.6mm nozzle support +; - Enhances nozzle temperature handling +; - Updates guarded movements for safety +; - Adds mech-mode (vibration) optional check +; - Improved accuracy on stabilization pattern +;===== 1.2 - Optimized all paths +;===== 1.1 - Release for public +;===== 1.0 - Initial version + +;===== start warm-up sequence ============================= +M1002 gcode_claim_action : 2 ; status: heating +M1002 set_filament_type:{filament_type[initial_no_support_extruder]} + +; conservative nozzle preheat based on material +M104 S{nozzle_temperature_initial_layer[initial_extruder] - 50} +M140 S{bed_temperature_initial_layer_single} + +G392 S0 ; disable clog detect +M9833.2 ; bambu: set noise/mech params + +;===== start printer sound ================================ +; remove sounds for quieter startup +;castle-complete +;music_long: 5.837832 +M17 +M400 S1 +M1006 S1 +M1006 L84 M70 N99 +M1006 A41 B8 L68 C25 D8 M35 E49 F8 N31 +M1006 A0 B8 C25 D8 M35 E49 F8 N31 +M1006 A37 B8 L64 C25 D8 M35 E44 F8 N38 +M1006 A0 B8 C25 D8 M35 E44 F8 N38 +M1006 A32 B8 L42 C25 D9 M35 E41 F9 N38 +M1006 A0 B8 C25 D8 M35 E41 F7 N38 +M1006 A41 B8 L68 C25 D8 M35 E49 F8 N31 +M1006 A0 B8 C25 D8 M35 E49 F8 N31 +M1006 A37 B8 L64 C25 D8 M35 E44 F8 N38 +M1006 A0 B8 C25 D8 M35 E44 F8 N38 +M1006 A32 B8 L42 C25 D8 M35 E41 F8 N38 +M1006 A0 B8 C25 D8 M35 E41 F8 N38 +M1006 A41 B24 L68 C25 D24 M35 E49 F25 N31 +;Tick 122, Time 1 sec +M73 P17 R0 +M1006 A41 B8 L68 C25 D8 M35 E49 F8 N31 +M1006 A41 B8 L68 C25 D8 M35 E49 F8 N31 +M1006 A41 B8 L68 C25 D8 M35 E49 F8 N31 +M1006 A41 B8 L68 C25 D8 M35 E49 F8 N31 +M1006 A0 B8 C0 D8 E49 F8 N31 +M1006 A41 B8 L68 C25 D8 M35 E49 F8 N31 +M1006 A0 B8 C0 D8 E49 F8 N31 +M1006 A41 B8 L68 C25 D8 M35 E49 F8 N31 +M1006 A0 B9 C0 D9 E49 F8 N31 +M1006 A42 B8 L79 C26 D8 M28 E50 F8 N52 +;Tick 203, Time 2 sec +M73 P34 R0 +M1006 A0 B8 C26 D8 M28 E50 F8 N52 +M1006 A38 B8 L61 C26 D8 M28 E45 F8 N45 +M1006 A0 B8 C26 D8 M28 E45 F8 N45 +M1006 A33 B8 L56 C26 D8 M28 E42 F8 N38 +M1006 A0 B8 C26 D8 M28 E42 F8 N38 +M1006 A42 B8 L79 C26 D8 M28 E50 F8 N52 +M1006 A0 B8 C26 D8 M28 E50 F8 N52 +M1006 A38 B8 L61 C26 D9 M28 E45 F9 N45 +M1006 A0 B8 C26 D8 M28 E45 F7 N45 +M1006 A33 B8 L56 C26 D8 M28 E42 F8 N38 +M1006 A0 B8 C26 D8 M28 E42 F8 N38 +M1006 A42 B24 L79 C26 D24 M28 E50 F24 N52 +;Tick 316, Time 3 sec +M73 P51 R0 +M1006 A42 B8 L79 C26 D8 M28 E50 F8 N52 +M1006 A42 B8 L79 C26 D8 M28 E50 F8 N52 +M1006 A42 B8 L79 C26 D8 M28 E50 F9 N52 +M1006 A42 B8 L79 C26 D8 M28 E50 F8 N52 +M1006 A0 B8 C0 D8 E50 F8 N52 +M1006 A42 B8 L79 C26 D8 M28 E50 F8 N52 +M1006 A0 B8 C0 D8 E50 F8 N52 +M1006 A42 B8 L79 C26 D8 M28 E50 F8 N52 +M1006 A0 B8 C0 D8 E50 F8 N52 +M1006 A44 B8 L83 C35 D8 M59 E52 F8 N69 +M1006 A0 B8 C0 D8 E52 F8 N69 +;Tick 405, Time 4 sec +M73 P68 R0 +M1006 A40 B8 L71 C32 D8 M45 E47 F9 N45 +M1006 A0 B8 C0 D8 E47 F7 N45 +M1006 A35 B8 L50 C28 D8 M21 E44 F8 N38 +M1006 A0 B8 C0 D8 E44 F8 N38 +M1006 A44 B8 L83 C35 D8 M59 E52 F8 N69 +M1006 A0 B8 C0 D8 E52 F8 N69 +M1006 A40 B8 L71 C32 D8 M45 E47 F8 N45 +M1006 A0 B8 C0 D8 E47 F8 N45 +M1006 A35 B8 L50 C28 D8 M21 E44 F8 N38 +M1006 A0 B8 C0 D8 E44 F8 N38 +M1006 A44 B24 L83 C35 D24 M59 E52 F24 N69 +;Tick 511, Time 5 sec +M73 P85 R0 +M1006 A44 B8 L83 C35 D8 M59 E52 F8 N69 +M1006 A44 B8 L83 C35 D8 M59 E52 F8 N69 +M1006 A44 B8 L83 C35 D8 M59 E52 F8 N69 +M1006 A46 B8 L83 C37 D8 M69 E54 F8 N69 +M1006 A0 B8 C0 D8 E0 F8 +M1006 A46 B8 L83 C37 D8 M69 E54 F8 N69 +M1006 A0 B9 C0 D9 E0 F9 +M1006 A46 B8 L83 C37 D8 M69 E54 F8 N69 +M1006 A0 B8 C0 D8 E0 F8 +M1006 A48 B97 L83 C39 D97 M69 E56 F97 N49 +M1006 W +M18 + +;===== initialize machine status ========================== +G90 ; absolute positioning +M83 ; relative extrusion +M211 X1 Y1 Z1 ; enable soft endstops + +M630 S0 P0 ; bambu: reset internal state +M204 S6000 ; set default acceleration +M17 X0.7 Y0.9 Z0.5 ; set motor current +M960 S5 P1 ; enable toolhead lamp + +M220 S100 ; feedrate to 100% +M221 S100 ; flowrate to 100% +M982.2 S1 ; enable cog noise reduction +M975 S1 ; enable motion gating +M106 P1 S0 ; disable fan while heating +M73.2 R1.0 ; bambu: reset time left magnitude + +;===== home and stage printer ============================= +M17 ; enable motors +G28 X Y ; safely home X and Y +G0 X55 Y175 F10000 ; move to safe place to home Z +G380 S2 Z5 F1200 ; guarded clearance before homing +G28 Z P0 T300 ; safely home Z +M400 ; wait for movements + +M211 S ; push endstop status +M211 X0 Y0 Z0 ; disable soft endstop +G1 Z5 F2000 ; add clearance before move +G1 X0 F10000 ; move to service area +G1 X-13.5 F3000 ; move into wiper +M400 + +;===== switch material in AMS ============================= +M620 M ; enable remap +M620 S[initial_no_support_extruder]A + G392 S1 ; enable clog detect + M1002 gcode_claim_action : 4 + M400 + M1002 set_filament_type:UNKNOWN + M109 S[nozzle_temperature_initial_layer] + M104 S250 + M400 + T[initial_no_support_extruder] + G1 X-13.5 F3000 + M400 + M620.1 E F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60} T{nozzle_temperature_range_high[initial_no_support_extruder]} + M109 S250 ; set nozzle to common flush temp + M106 P1 S0 + G92 E0 + G1 E50 F200 + M400 + M1002 set_filament_type:{filament_type[initial_no_support_extruder]} + M104 S{nozzle_temperature_range_high[initial_no_support_extruder]} + G92 E0 + G1 E50 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60} + M400 + M106 P1 S178 + G92 E0 + G1 E5 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60} + M109 S{nozzle_temperature_initial_layer[initial_no_support_extruder]-20} + M104 S{nozzle_temperature_initial_layer[initial_no_support_extruder]-40} + G92 E0 + G1 E-0.5 F300 + + G1 X0 F20000 + G1 X-13.5 F3000 + G1 X0 F20000 + G1 X-13.5 F3000 + G1 X0 F12000 + G1 X-13.5 F3000 + + ; reset nozzle to expected temperature + M104 S{nozzle_temperature_initial_layer[initial_no_support_extruder] - 50} + G392 S0 ; disable clog detect +M621 S[initial_no_support_extruder]A + +;===== build plate detection (flagged) ==================== +M1002 judge_flag build_plate_detect_flag +M622 S1 + G39.4 ; bambu: quick build plate detection + M400 + + ; quick mech-mode resonance check for improving vertical artifacts + ; runs ONLY when "Bed Leveling" is enabled + ; uses vibration amplitude to work on all nozzles + ; @warning: reliably triggers the eddy current sensor warning, testing alternatives to the long and loud sweeps from the default routine + ; G1 X128 Y128 Z10 F20000 ; use bed center for symetrical testing + ; M400 P200 + ; M970.3 Q1 A7 B30 C80 H12 K0 O2 + ; M974 Q1 S2 P0 +M623 + +;===== clean nozzle ======================================= +M1002 gcode_claim_action : 14 ; status: nozzle cleaning + +G90 +M83 +M106 P1 S255 ; short blast to neck any strands +G4 P800 +M106 P1 S0 ; keep fan off during heating + +; perform a short knock sequence by bending +; oozed filament before brushing sequence +G1 E-1.0 F500 ; small retract before taps +G1 Z5 F2000 ; clearance +G0 X90 Y-4 F10000 ; move to the purge area +G380 S3 Z-2 F1200 ; gentle tap on plate +G1 Z2 F2000 +G1 X91 F3000 +G380 S3 Z-2 F1200 +G1 Z2 F2000 +G1 X92 F3000 +G380 S3 Z-2 F1200 + +; brush material on rubber +G1 Z5 F2000 +G1 X25 Y176 F20000 +G1 Z0.2 F2000 +G1 Y186 +G91 +G1 X-30 F20000 +G1 Y-2 +G1 X27 +G1 Y1.5 +G1 X-28 +G1 Y-2 +G1 X30 +G1 Y1.5 +G1 X-30 +G90 +G1 Z5 F2000 + +; brush material on rubber, slight offset +G1 Z5 F2000 +G1 X25 Y177 F20000 +G1 Z0.2 F2000 +G1 Y187 +G91 +G1 X-30 F20000 +G1 Y-2 +G1 X27 +G1 Y1.5 +G1 X-28 +G1 Y-2 +G1 X30 +G1 Y1.5 +G1 X-30 +G90 +G1 Z5 F2000 + +; restore protections and establish raw Z reference +M211 R ; restore softend status +G29.2 S0 ; disable ABL for raw Z +G0 X55 Y175 F10000 +G28 Z P0 T300 +G29.2 S1 ; enable ABL + +;===== park and wait for heating ========================== +M1002 gcode_claim_action : 2 ; status: heating +G1 Z5 F2000 ; ensure clearance +G1 X10 Y10 F10000 ; park clear of brush + +; set and wait for bed to final temperature +M140 S[bed_temperature_initial_layer_single] +M104 S{nozzle_temperature_initial_layer[initial_extruder] - 50} +M190 S[bed_temperature_initial_layer_single] + +;===== bed leveling (flagged) ============================= +M1002 judge_flag g29_before_print_flag +M622 J1 + G29.2 S0 ; disable ABL for probing + M1002 gcode_claim_action : 1 ; status: auto bed leveling + G29 A1 X{first_layer_print_min[0]} Y{first_layer_print_min[1]} I{first_layer_print_size[0]} J{first_layer_print_size[1]} + M400 + M500 ; save mesh + G29.2 S1 ; enable ABL with fresh mesh +M623 + +;===== bed leveling (not flagged) ========================= +M1002 judge_flag g29_before_print_flag +M622 J0 + M1002 gcode_claim_action : 13 ; status: homing toolhead + G28 T300 ; permissive temp home + G29.2 S1 ; enable ABL with existing mesh +M623 + +;===== nozzle load line =================================== +M1002 gcode_claim_action : 2 ; status: heating +M975 S1 ; enable motion gating (explicit) +G90 ; re-assert positioning (explicit) +M83 +T1000 ; select local tool + +M211 S ; push endstop status +M211 X0 Y0 Z0 ; disable soft endstop +G1 Z5 F2000 ; add clearance before move +G1 X0 F10000 ; move to service area +G1 X-13.5 F3000 ; move into wiper + +; minimal prime with micro-retract inside service area +G92 E0 ; reset extruded amount before line +G1 E1.2 F500 +G1 E-0.25 F1500 + +; set and wait for nozzle to final temperature +M104 S{nozzle_temperature_initial_layer[initial_extruder]} +M109 S{nozzle_temperature_initial_layer[initial_extruder]} + +;===== prepare sensors for calibration ==================== +M1002 set_filament_type:UNKNOWN ; prepare filament for calibration +M412 S1 ; enable filament runout detect +M400 +M620.3 W1 ; enable filament tangle detect +G392 S0 ; disable clog detect during calibration +M400 S2 P100 ; small wait with sensor settle + +;===== flow dynamics calibration (flagged) ================ +M1002 set_filament_type:{filament_type[initial_no_support_extruder]} +M1002 judge_flag extrude_cali_flag +M622 J1 + M1002 gcode_claim_action : 8 ; status: calibrating extrusion + + M900 K0.0 L1000.0 M1.0 ; pressure advance baseline + G90 + M83 ; re-assert positioning (explicit) + + G1 Z5 F2000 ; safe lift + G0 X68 Y-4 F10000 ; move near start position + G0 Z0.3 F2000 ; move to start position + M400 + + G0 X88 E10 F{outer_wall_volumetric_speed/(24/20)*60} + G0 X93 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4*60} + G0 X98 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)*60} + G0 X103 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4*60} + G0 X108 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)*60} + G0 X113 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4*60} + G0 Y0 Z0 F20000 ; finish the patterned sweep + M400 + + G1 X-13.5 Y0 Z10 F10000 ; park in service area + M400 + + ; primary dynamic extrusion compensation + G1 E10 F{outer_wall_volumetric_speed/2.4*60} + M983 F{outer_wall_volumetric_speed/2.4} A0.3 H[nozzle_diameter] + M106 P1 S180 ; enable fan to neck strand + M400 S7 ; short settle + + G1 X0 F20000 ; wipe & shake + G1 X-13.5 F3000 + G1 X0 F20000 + G1 X-13.5 F3000 + G1 X0 F12000 + G1 X-13.5 F3000 + M400 + M106 P1 S0 ; disable fan + + ; retry once if needed + M1002 judge_last_extrude_cali_success + M622 J0 + M983 F{outer_wall_volumetric_speed/2.4} A0.3 H[nozzle_diameter] + M106 P1 S180 + M400 S7 + G1 X0 F20000 + G1 X-13.5 F3000 + G1 X0 F20000 + G1 X-13.5 F3000 + G1 X0 F12000 + G1 X-13.5 F3000 + M400 + M106 P1 S0 + M623 + + ; final corrections and cleanup + G1 X-13.5 F3000 + M400 + M984 A0.1 E1 S1 F{outer_wall_volumetric_speed/2.4} H[nozzle_diameter] + M106 P1 S180 + M400 S7 + G1 X0 F20000 + G1 X-13.5 F3000 + G1 X0 F20000 + G1 X-13.5 F3000 + G1 X0 F12000 + G1 X-13.5 F3000 + M400 + M106 P1 S0 +M623 ; end flow dynamics calibration +M211 R ; restore soft endstops status + +;===== extrude calibration test =========================== +; hold first-layer temp and set modes (explicit) +M109 S{nozzle_temperature_initial_layer[initial_extruder]} +M190 S{bed_temperature_initial_layer_single} +G90 +M83 + +; clear any ooze before calibrating +M106 P1 S180 ; moderate fan for PLA and PETG +M400 S2 +G1 E-0.05 F1500 +G1 X0 F20000 +G1 X-13.5 F3000 +G1 X0 F20000 +G1 X-13.5 F3000 +G1 X0 F12000 +G1 X-13.5 F3000 +M106 P1 S0 +M400 + +; draw short stabilization pattern +G1 Z5 F3000 +G0 X68 Y-2.5 F20000 +G0 Z0.3 F3000 +G0 X88 E10 F{outer_wall_volumetric_speed/(24/20)*60} +G0 X93 E0.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4*60} +G0 X98 E0.3742 F{outer_wall_volumetric_speed/(0.3*0.5)*60} +G0 X103 E0.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4*60} +G0 X108 E0.3742 F{outer_wall_volumetric_speed/(0.3*0.5)*60} +G0 X113 E0.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4*60} +G0 X115 Z0 F20000 +G1 Z5 F3000 +M400 + +;===== for textured pei plate ============================= +{if curr_bed_type=="Textured PEI Plate"} + G29.1 Z{-0.02} +{endif} + +;===== normalize lights/fans & re-enable protections ===== +M960 S1 P0 ; light/laser ch1 off +M960 S2 P0 ; light/laser ch2 off +M960 S5 P0 ; toolhead lamp off +M106 P1 S0 ; part fan off +M106 P2 S0 ; aux fan off +M106 P3 S0 ; chamber fan off + +G392 S1 ; re-enable clog detect +G29.2 S1 ; enable ABL for print + +;===== final staging ===================================== +M1002 gcode_claim_action : 0 ; status: printing +M975 S1 ; keep vibration suppression on +G90 +M83 +T1000 +M211 R ; restore endstop status +M211 X0 Y0 Z0 ; disable soft endstops +M1007 S1 ; bambu: keep enabled + +; ===== hand-off to slicer first move ===================== \ No newline at end of file diff --git "a/doc/A1mini\346\215\242\346\226\231.gcode" "b/doc/A1mini\346\215\242\346\226\231.gcode" index c177355..6025b13 100644 --- "a/doc/A1mini\346\215\242\346\226\231.gcode" +++ "b/doc/A1mini\346\215\242\346\226\231.gcode" @@ -1,49 +1,53 @@ -;====TOP-AMS====A1mini====2025年4月4日19:18:04====start===== +; ===== machine: Bambu A1 mini ============= +; ===== date: 20240613 ===================== +; ===== Filamentor V0.3 ==================== +; ===== Based on the official A1 mini change filament gcode (20231225)====== -{if toolchange_count > 1} -G17 -G2 Z{max_layer_z + 0.4} I0.86 J0.86 P1 F10000 ;回抽抬升 -;endif;放在结尾,目前设定首次就不换色,如果想要首次换色,取消本行注释,且注释本文件最后一行的endif +;G392 S0 +;M1007 S0 -G1 Z{max_layer_z + 3.0} -;Z抬升 +; 实现:如果前后通道一致,直接返回,跳过所有换料操作;如果不一致,继续执行 +{if next_extruder == previous_extruder || next_extruder >= 255} +; 前后通道一致或无效通道,直接返回,跳过所有换料操作 +{else} +M140 S{next_extruder + 1};EXT +;M204 S9000 +;{if toolchange_count > 1} +;G17 +;G2 Z{max_layer_z + 0.4} I0.86 J0.86 P1 F10000 ; spiral lift a little from second lift +;{endif} +;G1 Z{max_layer_z + 3.0} F1200 -{if long_retractions_when_cut[previous_extruder]} -G1 E-{retraction_distances_when_cut[previous_extruder]} F1200;加速度也会影响实际回抽量 -{endif} M400 -;长回抽 - -G1 X-3.5 F18000 -G1 X-13.5 F3000;移动到马桶(A1mini坐标) - -M140 S{next_extruder + 1};//使用热床目标温度传递通道信息 -M400 U1;//暂停 - - +M106 P1 S0 +M106 P2 S0 +;{if old_filament_temp > 142 && next_extruder < 255} +;M104 S[old_filament_temp] +;{endif} +G1 X-13.5 F18000 +M400 U1 -;下面为原版冲刷,加大了刷料时的风扇速度 S178->S255 +{if next_extruder < 255} +M400 +G92 E0 {if flush_length_1 > 1} -;FL1 > 1 ; FLUSH_START ; always use highest temperature to flush M400 M1002 set_filament_type:UNKNOWN -M109 S[nozzle_temperature_range_high] -;M109 S[] M106 P1 S60 -{if flush_length_1 > 23.7} -G1 E23.7 F{old_filament_e_feedrate} ; do not need pulsatile flushing for start part -G1 E{(flush_length_1 - 23.7) * 0.02} F50 -G1 E{(flush_length_1 - 23.7) * 0.23} F{old_filament_e_feedrate} -G1 E{(flush_length_1 - 23.7) * 0.02} F50 -G1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate} -G1 E{(flush_length_1 - 23.7) * 0.02} F50 -G1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate} -G1 E{(flush_length_1 - 23.7) * 0.02} F50 -G1 E{(flush_length_1 - 23.7) * 0.23} F{new_filament_e_feedrate} +{if flush_length_1 > 15} +G1 E15 F{old_filament_e_feedrate} ; 从23.7mm减少到15mm,减少初始冲洗 +G1 E{(flush_length_1 - 15) * 0.02} F50 +G1 E{(flush_length_1 - 15) * 0.23} F{old_filament_e_feedrate} +G1 E{(flush_length_1 - 15) * 0.02} F50 +G1 E{(flush_length_1 - 15) * 0.23} F{new_filament_e_feedrate} +G1 E{(flush_length_1 - 15) * 0.02} F50 +G1 E{(flush_length_1 - 15) * 0.23} F{new_filament_e_feedrate} +G1 E{(flush_length_1 - 15) * 0.02} F50 +G1 E{(flush_length_1 - 15) * 0.23} F{new_filament_e_feedrate} {else} G1 E{flush_length_1} F{old_filament_e_feedrate} {endif} @@ -54,11 +58,10 @@ M400 M1002 set_filament_type:{filament_type[next_extruder]} {endif} -{if flush_length_1 > 45 && flush_length_2 > 1} -;FL1>45 && FL2>1 +{if flush_length_1 > 60 && flush_length_2 > 1} ; 提高擦拭触发阈值,从45mm提高到60mm ; WIPE M400 -M106 P1 S255 +M106 P1 S178 M400 S3 G1 X-3.5 F18000 G1 X-13.5 F3000 @@ -71,7 +74,6 @@ M106 P1 S0 {endif} {if flush_length_2 > 1} -;FL2>1 M106 P1 S60 ; FLUSH_START G1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate} @@ -80,20 +82,15 @@ G1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate} G1 E{flush_length_2 * 0.02} F50 G1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate} G1 E{flush_length_2 * 0.02} F50 -G1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate} -G1 E{flush_length_2 * 0.02} F50 -G1 E{flush_length_2 * 0.18} F{new_filament_e_feedrate} -G1 E{flush_length_2 * 0.02} F50 -; FLUSH_END +; FLUSH_END (从5次循环减少到3次) G1 E-[new_retract_length_toolchange] F1800 G1 E[new_retract_length_toolchange] F300 {endif} -{if flush_length_2 > 45 && flush_length_3 > 1} -;FL2>45 &&FL3>1 +{if flush_length_2 > 60 && flush_length_3 > 1} ; 提高擦拭触发阈值,从45mm提高到60mm ; WIPE M400 -M106 P1 S255 +M106 P1 S178 M400 S3 G1 X-3.5 F18000 G1 X-13.5 F3000 @@ -102,11 +99,10 @@ G1 X-13.5 F3000 G1 X-3.5 F18000 G1 X-13.5 F3000 M400 -M106 P1 S0 +M106 P1 S0 {endif} {if flush_length_3 > 1} -;FL3>1 M106 P1 S60 ; FLUSH_START G1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate} @@ -115,20 +111,15 @@ G1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate} G1 E{flush_length_3 * 0.02} F50 G1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate} G1 E{flush_length_3 * 0.02} F50 -G1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate} -G1 E{flush_length_3 * 0.02} F50 -G1 E{flush_length_3 * 0.18} F{new_filament_e_feedrate} -G1 E{flush_length_3 * 0.02} F50 -; FLUSH_END +; FLUSH_END (从5次循环减少到3次) G1 E-[new_retract_length_toolchange] F1800 G1 E[new_retract_length_toolchange] F300 {endif} -{if flush_length_3 > 45 && flush_length_4 > 1} -;FL3>45 && FL4>1 +{if flush_length_3 > 60 && flush_length_4 > 1} ; 提高擦拭触发阈值,从45mm提高到60mm ; WIPE M400 -M106 P1 S255 +M106 P1 S178 M400 S3 G1 X-3.5 F18000 G1 X-13.5 F3000 @@ -141,7 +132,6 @@ M106 P1 S0 {endif} {if flush_length_4 > 1} -;FL>4 M106 P1 S60 ; FLUSH_START G1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate} @@ -150,24 +140,32 @@ G1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate} G1 E{flush_length_4 * 0.02} F50 G1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate} G1 E{flush_length_4 * 0.02} F50 -G1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate} -G1 E{flush_length_4 * 0.02} F50 -G1 E{flush_length_4 * 0.18} F{new_filament_e_feedrate} -G1 E{flush_length_4 * 0.02} F50 -; FLUSH_END +; FLUSH_END (从5次循环减少到3次) {endif} -;M629//不知道这什么用 +{if toolchange_count == 1} +; For the first material change, FLUSH a bit more. +M400 +M109 S250 +M106 P1 S60 + +; FLUSH_START +G1 E20 F{old_filament_e_feedrate} ; 从50mm减少到20mm +; FLUSH_END + +G1 E-[new_retract_length_toolchange] F1800 +G1 E[new_retract_length_toolchange] F300 +{endif} M400 M106 P1 S60 M109 S[new_filament_temp] -G1 E5 F{new_filament_e_feedrate} ;Compensate for filament spillage during waiting temperature +G1 E2 F{new_filament_e_feedrate} ;Compensate for filament spillage during waiting temperature (从5mm减少到2mm) M400 G92 E0 G1 E-[new_retract_length_toolchange] F1800 M400 -M106 P1 S255 +M106 P1 S178 M400 S3 G1 X-3.5 F18000 G1 X-13.5 F3000 @@ -178,8 +176,21 @@ G1 X-13.5 F3000 G1 X-3.5 F18000 G1 X-13.5 F3000 M400 +G1 Z{max_layer_z + 3.0} F3000 +M106 P1 S0 +{if layer_z <= (initial_layer_print_height + 0.001)} +M204 S[initial_layer_acceleration] +{else} +M204 S[default_acceleration] +{endif} +{else} +G1 X[x_after_toolchange] Y[y_after_toolchange] Z[z_after_toolchange] F12000 +{endif} +M620 S[next_extruder]A +T[next_extruder] +M621 S[next_extruder]A +G392 S0 +M1007 S1 +{endif} - -{endif};if toolchange_count > 1 -;====TOP-AMS====end===== \ No newline at end of file diff --git "a/doc/A1mini\346\215\242\346\226\231_topams\347\211\210\346\234\254.gcode" "b/doc/A1mini\346\215\242\346\226\231_topams\347\211\210\346\234\254.gcode" new file mode 100644 index 0000000..23af01b --- /dev/null +++ "b/doc/A1mini\346\215\242\346\226\231_topams\347\211\210\346\234\254.gcode" @@ -0,0 +1,249 @@ +;===== A1mini Top-AMS版本 ===== +;===== 基于原版20251031修改 ===== +;===== 手动换料版本,保留完整冲刷逻辑 ===== +;===== 移除AMS特有指令,添加外部设备通知和手动换料暂停 ===== + +; 实现:如果前后通道一致,直接返回,跳过所有换料操作;如果不一致,继续执行 +{if next_extruder == previous_extruder || next_extruder >= 255} +; 前后通道一致或无效通道,直接返回,跳过所有换料操作 +{else} +; 设置打印板温度为 next_extruder + 1,用于通知外部设备换料 +M140 S{next_extruder + 1};EXT + +; 已注释:退料前的准备动作由 main.cpp 中的退料流程处理 +; G392 S0 +; M1007 S0 +; M204 S9000 +; {if toolchange_count > 1} +; {if z_hop_types[current_extruder] == 0 || z_hop_types[current_extruder] == 3} +; G17 +; G2 Z{z_after_toolchange + 0.4} I0.86 J0.86 P1 F10000 ; spiral lift a little from second lift +; {endif} +; {endif} +; G1 Z{max_layer_z + 3.0} F1200 + +M400 +M106 P1 S0 +M106 P2 S0 +; 已注释:旧耗材温度设置由 main.cpp 中的退料流程处理 +; {if old_filament_temp > 142 && next_extruder < 255} +; M104 S[old_filament_temp] +; {endif} + +; 已注释:移动到换料位置的操作由 main.cpp 中的退料流程处理 +; G1 X180 F18000 +; +; ;===== 手动换料部分 ===== +; ; 移动到换料位置 +; G1 Y90 F9000 + +; 已注释:M400和G92 E0重置由 main.cpp 中的退料流程处理 +; M400 +; G92 E0 + +;===== 手动换料暂停点 ===== +; 移动到切割位置 +G1 X-13.5 F18000 +M400 + +; 切割线材(推出一小段然后回退) +; 已注释:退料逻辑由 main.cpp 中的快速退料命令处理 +; G1 E10 F200 +; G1 E-10 F200 +; G1 E-20 F500 + +; 暂停等待用户手动换料 +M400 U1 + +;===== 继续执行冲刷逻辑 ===== +{if next_extruder < 255} +M400 +G92 E0 + +{if flush_length_1 > 1} +; FLUSH_START +; always use highest temperature to flush +M400 +M1002 set_filament_type:UNKNOWN +M109 S[flush_temperatures[next_extruder]] +M106 P1 S60 +{if flush_length_1 > 23.7} +G1 E23.7 F{flush_volumetric_speeds[previous_extruder]/2.4053*60} ; do not need pulsatile flushing for start part +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +{else} +G1 E{flush_length_1} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +{endif} +; FLUSH_END +G1 E-[old_retract_length_toolchange] F1800 +G1 E[old_retract_length_toolchange] F300 +M400 +M1002 set_filament_type:{filament_type[next_extruder]} +{endif} + +{if flush_length_1 > 45 && flush_length_2 > 1} +; WIPE +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +M106 P1 S0 +{endif} + +{if flush_length_2 > 1} +M106 P1 S60 +; FLUSH_START +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +; FLUSH_END +G1 E-[new_retract_length_toolchange] F1800 +G1 E[new_retract_length_toolchange] F300 +{endif} + +{if flush_length_2 > 45 && flush_length_3 > 1} +; WIPE +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +M106 P1 S0 +{endif} + +{if flush_length_3 > 1} +M106 P1 S60 +; FLUSH_START +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +; FLUSH_END +G1 E-[new_retract_length_toolchange] F1800 +G1 E[new_retract_length_toolchange] F300 +{endif} + +{if flush_length_3 > 45 && flush_length_4 > 1} +; WIPE +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +M106 P1 S0 +{endif} + +{if flush_length_4 > 1} +M106 P1 S60 +; FLUSH_START +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +; FLUSH_END +{endif} + +M400 +M106 P1 S60 +M109 S[new_filament_temp] +G1 E5 F{flush_volumetric_speeds[next_extruder]/2.4053*60} ;Compensate for filament spillage during waiting temperature +M400 +G92 E0 +G1 E-[new_retract_length_toolchange] F1800 +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +G1 Z{max_layer_z + 3.0} F3000 +M106 P1 S0 +{if layer_z <= (initial_layer_print_height + 0.001)} +M204 S[initial_layer_acceleration] +{else} +M204 S[default_acceleration] +{endif} +{else} +G1 X[x_after_toolchange] Y[y_after_toolchange] Z[z_after_toolchange] F12000 +{endif} + +M622.1 S0 +M9833 F{outer_wall_volumetric_speed/2.4} A0.3 ; cali dynamic extrusion compensation +M1002 judge_flag filament_need_cali_flag +M622 J1 + G92 E0 + G1 E-[new_retract_length_toolchange] F1800 + M400 + + M106 P1 S178 + M400 S7 + G1 X0 F18000 + G1 X-13.5 F3000 + G1 X0 F18000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + M400 + M106 P1 S0 +M623 + +;===== AMS命令跳过 ===== +; 由于没有AMS,以下三行仅用于抑制T[next_extruder]命令 +; 如果省略这些行,T[next_extruder]命令会在本代码后执行, +; 导致系统挂起,因为工具更换命令会等待AMS +M620 S[next_extruder]A +T[next_extruder] +M621 S[next_extruder]A + +G392 S0 + +M1007 S1 +{endif} \ No newline at end of file diff --git "a/doc/A1mini\346\215\242\346\226\231_\345\216\237\347\211\210.gcode" "b/doc/A1mini\346\215\242\346\226\231_\345\216\237\347\211\210.gcode" new file mode 100644 index 0000000..58f212b --- /dev/null +++ "b/doc/A1mini\346\215\242\346\226\231_\345\216\237\347\211\210.gcode" @@ -0,0 +1,229 @@ +;===== A1mini 20251031 ===== +G392 S0 +M1007 S0 +M620 S[next_extruder]A +M204 S9000 +G1 Z{max_layer_z + 3.0} F1200 + +M400 +M106 P1 S0 +M106 P2 S0 +{if old_filament_temp > 142 && next_extruder < 255} +M104 S[old_filament_temp] +{endif} + +G1 X180 F18000 + +{if long_retractions_when_cut[previous_extruder]} +M620.11 S1 I[previous_extruder] E-{retraction_distances_when_cut[previous_extruder]} F1200 +{else} +M620.11 S0 +{endif} +M400 + +M620.1 E F{flush_volumetric_speeds[previous_extruder]/2.4053*60} T{flush_temperatures[previous_extruder]} +M620.10 A0 F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +T[next_extruder] +M620.1 E F{flush_volumetric_speeds[next_extruder]/2.4053*60} T{flush_temperatures[next_extruder]} +M620.10 A1 F{flush_volumetric_speeds[next_extruder]/2.4053*60} L[flush_length] H[nozzle_diameter] T{flush_temperatures[next_extruder]} + +G1 Y90 F9000 + +{if next_extruder < 255} + +{if long_retractions_when_cut[previous_extruder]} +M620.11 S1 I[previous_extruder] E{retraction_distances_when_cut[previous_extruder]} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +M628 S1 +G92 E0 +G1 E{retraction_distances_when_cut[previous_extruder]} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +M400 +M629 S1 +{else} +M620.11 S0 +{endif} + +M400 +G92 E0 +M628 S0 + +{if flush_length_1 > 1} +; FLUSH_START +; always use highest temperature to flush +M400 +M1002 set_filament_type:UNKNOWN +M109 S[flush_temperatures[next_extruder]] +M106 P1 S60 +{if flush_length_1 > 23.7} +G1 E23.7 F{flush_volumetric_speeds[previous_extruder]/2.4053*60} ; do not need pulsatile flushing for start part +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +{else} +G1 E{flush_length_1} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +{endif} +; FLUSH_END +G1 E-[old_retract_length_toolchange] F1800 +G1 E[old_retract_length_toolchange] F300 +M400 +M1002 set_filament_type:{filament_type[next_extruder]} +{endif} + +{if flush_length_1 > 45 && flush_length_2 > 1} +; WIPE +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +M106 P1 S0 +{endif} + +{if flush_length_2 > 1} +M106 P1 S60 +; FLUSH_START +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +; FLUSH_END +G1 E-[new_retract_length_toolchange] F1800 +G1 E[new_retract_length_toolchange] F300 +{endif} + +{if flush_length_2 > 45 && flush_length_3 > 1} +; WIPE +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +M106 P1 S0 +{endif} + +{if flush_length_3 > 1} +M106 P1 S60 +; FLUSH_START +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +; FLUSH_END +G1 E-[new_retract_length_toolchange] F1800 +G1 E[new_retract_length_toolchange] F300 +{endif} + +{if flush_length_3 > 45 && flush_length_4 > 1} +; WIPE +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +M106 P1 S0 +{endif} + +{if flush_length_4 > 1} +M106 P1 S60 +; FLUSH_START +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +; FLUSH_END +{endif} + +M629 + +M400 +M106 P1 S60 +M109 S[new_filament_temp] +G1 E5 F{flush_volumetric_speeds[next_extruder]/2.4053*60} ;Compensate for filament spillage during waiting temperature +M400 +G92 E0 +G1 E-[new_retract_length_toolchange] F1800 +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +G1 Z{max_layer_z + 3.0} F3000 +M106 P1 S0 +{if layer_z <= (initial_layer_print_height + 0.001)} +M204 S[initial_layer_acceleration] +{else} +M204 S[default_acceleration] +{endif} +{else} +G1 X[x_after_toolchange] Y[y_after_toolchange] Z[z_after_toolchange] F12000 +{endif} + +M622.1 S0 +M9833 F{outer_wall_volumetric_speed/2.4} A0.3 ; cali dynamic extrusion compensation +M1002 judge_flag filament_need_cali_flag +M622 J1 + G92 E0 + G1 E-[new_retract_length_toolchange] F1800 + M400 + + M106 P1 S178 + M400 S7 + G1 X0 F18000 + G1 X-13.5 F3000 + G1 X0 F18000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + M400 + M106 P1 S0 +M623 + +M621 S[next_extruder]A +G392 S0 + +M1007 S1 diff --git "a/doc/A1mini\346\215\242\346\226\231_\345\242\236\345\274\272\347\211\210.gcode" "b/doc/A1mini\346\215\242\346\226\231_\345\242\236\345\274\272\347\211\210.gcode" new file mode 100644 index 0000000..9534932 --- /dev/null +++ "b/doc/A1mini\346\215\242\346\226\231_\345\242\236\345\274\272\347\211\210.gcode" @@ -0,0 +1,239 @@ +;===== A1mini 20251031 ===== +; 增强版:基于原版20251031修改,新增通道一致性检查和打印板温度通知功能 +; 实现:如果前后通道一致,直接返回,跳过所有换料操作;如果不一致,继续执行 +{if next_extruder == previous_extruder || next_extruder >= 255} +; 前后通道一致或无效通道,直接返回,跳过所有换料操作 +{else} +; 设置打印板温度为 next_extruder + 1,用于通知外部挤出机换料 +M140 S{next_extruder + 1};EXT + +G392 S0 +M1007 S0 +M620 S[next_extruder]A +M204 S9000 +G1 Z{max_layer_z + 3.0} F1200 + +M400 +M106 P1 S0 +M106 P2 S0 +{if old_filament_temp > 142 && next_extruder < 255} +M104 S[old_filament_temp] +{endif} + +G1 X180 F18000 + +{if long_retractions_when_cut[previous_extruder]} +M620.11 S1 I[previous_extruder] E-{retraction_distances_when_cut[previous_extruder]} F1200 +{else} +M620.11 S0 +{endif} +M400 + +M620.1 E F{flush_volumetric_speeds[previous_extruder]/2.4053*60} T{flush_temperatures[previous_extruder]} +M620.10 A0 F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +T[next_extruder] +M620.1 E F{flush_volumetric_speeds[next_extruder]/2.4053*60} T{flush_temperatures[next_extruder]} +M620.10 A1 F{flush_volumetric_speeds[next_extruder]/2.4053*60} L[flush_length] H[nozzle_diameter] T{flush_temperatures[next_extruder]} + +G1 Y90 F9000 + +{if next_extruder < 255} + +{if long_retractions_when_cut[previous_extruder]} +M620.11 S1 I[previous_extruder] E{retraction_distances_when_cut[previous_extruder]} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +M628 S1 +G92 E0 +G1 E{retraction_distances_when_cut[previous_extruder]} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +M400 +M629 S1 +{else} +M620.11 S0 +{endif} + +M400 +G92 E0 +M628 S0 + +{if flush_length_1 > 1} +; FLUSH_START +; always use highest temperature to flush +M400 +M1002 set_filament_type:UNKNOWN +M109 S[flush_temperatures[next_extruder]] +M106 P1 S60 +{if flush_length_1 > 23.7} +G1 E23.7 F{flush_volumetric_speeds[previous_extruder]/2.4053*60} ; do not need pulsatile flushing for start part +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{(flush_length_1 - 23.7) * 0.02} F50 +G1 E{(flush_length_1 - 23.7) * 0.23} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +{else} +G1 E{flush_length_1} F{flush_volumetric_speeds[previous_extruder]/2.4053*60} +{endif} +; FLUSH_END +G1 E-[old_retract_length_toolchange] F1800 +G1 E[old_retract_length_toolchange] F300 +M400 +M1002 set_filament_type:{filament_type[next_extruder]} +{endif} + +{if flush_length_1 > 45 && flush_length_2 > 1} +; WIPE +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +M106 P1 S0 +{endif} + +{if flush_length_2 > 1} +M106 P1 S60 +; FLUSH_START +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +G1 E{flush_length_2 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_2 * 0.02} F50 +; FLUSH_END +G1 E-[new_retract_length_toolchange] F1800 +G1 E[new_retract_length_toolchange] F300 +{endif} + +{if flush_length_2 > 45 && flush_length_3 > 1} +; WIPE +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +M106 P1 S0 +{endif} + +{if flush_length_3 > 1} +M106 P1 S60 +; FLUSH_START +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +G1 E{flush_length_3 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_3 * 0.02} F50 +; FLUSH_END +G1 E-[new_retract_length_toolchange] F1800 +G1 E[new_retract_length_toolchange] F300 +{endif} + +{if flush_length_3 > 45 && flush_length_4 > 1} +; WIPE +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +M106 P1 S0 +{endif} + +{if flush_length_4 > 1} +M106 P1 S60 +; FLUSH_START +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +G1 E{flush_length_4 * 0.18} F{flush_volumetric_speeds[next_extruder]/2.4053*60} +G1 E{flush_length_4 * 0.02} F50 +; FLUSH_END +{endif} + +M629 + +M400 +M106 P1 S60 +M109 S[new_filament_temp] +G1 E5 F{flush_volumetric_speeds[next_extruder]/2.4053*60} ;Compensate for filament spillage during waiting temperature +M400 +G92 E0 +G1 E-[new_retract_length_toolchange] F1800 +M400 +M106 P1 S178 +M400 S3 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +G1 X-3.5 F18000 +G1 X-13.5 F3000 +M400 +G1 Z{max_layer_z + 3.0} F3000 +M106 P1 S0 +{if layer_z <= (initial_layer_print_height + 0.001)} +M204 S[initial_layer_acceleration] +{else} +M204 S[default_acceleration] +{endif} +{else} +G1 X[x_after_toolchange] Y[y_after_toolchange] Z[z_after_toolchange] F12000 +{endif} + +M622.1 S0 +M9833 F{outer_wall_volumetric_speed/2.4} A0.3 ; cali dynamic extrusion compensation +M1002 judge_flag filament_need_cali_flag +M622 J1 + G92 E0 + G1 E-[new_retract_length_toolchange] F1800 + M400 + + M106 P1 S178 + M400 S7 + G1 X0 F18000 + G1 X-13.5 F3000 + G1 X0 F18000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + G1 X-13.5 F3000 + G1 X0 F12000 ;wipe and shake + M400 + M106 P1 S0 +M623 + +M621 S[next_extruder]A +G392 S0 + +M1007 S1 +{endif} + diff --git a/doc/print.json b/doc/print.json new file mode 100644 index 0000000..b42ab63 --- /dev/null +++ b/doc/print.json @@ -0,0 +1,155 @@ +{ + "print": { + "ipcam": { + "ipcam_dev": "1", + "ipcam_record": "disable", + "timelapse": "disable", + "resolution": "1080p", + "tutk_server": "disable", + "mode_bits": 3 + }, + "upload": { + "status": "idle", + "progress": 0, + "message": "" + }, + "net": { + "conf": 0, + "info": [ + { + "ip": 369141952, + "mask": 16777215 + } + ] + }, + "nozzle_temper": 26.53125, + "nozzle_target_temper": 0, + "bed_temper": 25.5, + "bed_target_temper": 0, + "chamber_temper": 5, + "mc_print_stage": "1", + "heatbreak_fan_speed": "0", + "cooling_fan_speed": "0", + "big_fan1_speed": "0", + "big_fan2_speed": "0", + "mc_percent": 0, + "mc_remaining_time": 4, + "ams_status": 0, + "ams_rfid_status": 0, + "hw_switch_state": 1, + "spd_mag": 100, + "spd_lvl": 2, + "print_error": 0, + "lifecycle": "product", + "wifi_signal": "-65dBm", + "gcode_state": "FAILED", + "gcode_file_prepare_percent": "100", + "queue_number": 0, + "queue_total": 0, + "queue_est": 0, + "queue_sts": 0, + "project_id": "137231652", + "profile_id": "104198813", + "task_id": "137231652", + "subtask_id": "137231652", + "subtask_name": "0.12mm 层高, 2 层墙, 15% 填充", + "gcode_file": "", + "stg": [], + "stg_cur": 0, + "print_type": "idle", + "home_flag": 863977875, + "mc_print_line_number": "0", + "mc_print_sub_stage": 0, + "sdcard": true, + "force_upgrade": false, + "mess_production_state": "active", + "layer_num": 1, + "total_layer_num": 2, + "s_obj": [], + "filam_bak": [], + "fan_gear": 0, + "nozzle_diameter": "0.4", + "nozzle_type": "stainless_steel", + "cali_version": 0, + "k": "0.0665", + "flag3": 15, + "upgrade_state": { + "sequence_id": 0, + "progress": "", + "status": "IDLE", + "consistency_request": false, + "dis_state": 1, + "err_code": 0, + "force_upgrade": false, + "message": "0%, 0B/s", + "module": "", + "new_version_state": 1, + "cur_state_code": 0, + "idx2": 3541206801, + "new_ver_list": [ + { + "name": "ota", + "cur_ver": "01.04.00.00", + "new_ver": "01.07.01.00", + "cur_release_type": 3, + "new_release_type": 3 + } + ] + }, + "hms": [], + "online": { + "ahb": false, + "rfid": false, + "version": 1181382388 + }, + "ams": { + "ams": [], + "ams_exist_bits": "0", + "tray_exist_bits": "0", + "tray_is_bbl_bits": "0", + "tray_tar": "255", + "tray_now": "255", + "tray_pre": "255", + "tray_read_done_bits": "0", + "tray_reading_bits": "0", + "version": 5, + "insert_flag": true, + "power_on_flag": true + }, + "xcam": { + "buildplate_marker_detector": true + }, + "vt_tray": { + "id": "254", + "tag_uid": "0000000000000000", + "tray_id_name": "", + "tray_info_idx": "GFG99", + "tray_type": "PETG", + "tray_sub_brands": "", + "tray_color": "000000FF", + "tray_weight": "0", + "tray_diameter": "0.00", + "tray_temp": "0", + "tray_time": "0", + "bed_temp_type": "0", + "bed_temp": "0", + "nozzle_temp_max": "270", + "nozzle_temp_min": "220", + "xcam_info": "000000000000000000000000", + "tray_uuid": "00000000000000000000000000000000", + "remain": 0, + "k": 0.019999999552965164, + "n": 1, + "cali_idx": -1 + }, + "lights_report": [ + { + "node": "chamber_light", + "mode": "on" + } + ], + "command": "push_status", + "msg": 0, + "sequence_id": "1717" + } +} \ No newline at end of file diff --git a/doc/print1.json b/doc/print1.json new file mode 100644 index 0000000..d448d50 --- /dev/null +++ b/doc/print1.json @@ -0,0 +1,155 @@ +{ + "print": { + "ipcam": { + "ipcam_dev": "1", + "ipcam_record": "disable", + "timelapse": "disable", + "resolution": "1080p", + "tutk_server": "disable", + "mode_bits": 3 + }, + "upload": { + "status": "idle", + "progress": 0, + "message": "" + }, + "net": { + "conf": 0, + "info": [ + { + "ip": 369141952, + "mask": 16777215 + } + ] + }, + "nozzle_temper": 24, + "nozzle_target_temper": 0, + "bed_temper": 22.78125, + "bed_target_temper": 0, + "chamber_temper": 5, + "mc_print_stage": "1", + "heatbreak_fan_speed": "0", + "cooling_fan_speed": "0", + "big_fan1_speed": "0", + "big_fan2_speed": "0", + "mc_percent": 0, + "mc_remaining_time": 4, + "ams_status": 0, + "ams_rfid_status": 0, + "hw_switch_state": 0, + "spd_mag": 100, + "spd_lvl": 2, + "print_error": 0, + "lifecycle": "product", + "wifi_signal": "-61dBm", + "gcode_state": "FAILED", + "gcode_file_prepare_percent": "100", + "queue_number": 0, + "queue_total": 0, + "queue_est": 0, + "queue_sts": 0, + "project_id": "137231652", + "profile_id": "104198813", + "task_id": "137231652", + "subtask_id": "137231652", + "subtask_name": "0.12mm 层高, 2 层墙, 15% 填充", + "gcode_file": "", + "stg": [], + "stg_cur": 0, + "print_type": "idle", + "home_flag": 863977872, + "mc_print_line_number": "0", + "mc_print_sub_stage": 0, + "sdcard": true, + "force_upgrade": false, + "mess_production_state": "active", + "layer_num": 1, + "total_layer_num": 2, + "s_obj": [], + "filam_bak": [], + "fan_gear": 0, + "nozzle_diameter": "0.4", + "nozzle_type": "stainless_steel", + "cali_version": 0, + "k": "0.0665", + "flag3": 15, + "upgrade_state": { + "sequence_id": 0, + "progress": "", + "status": "IDLE", + "consistency_request": false, + "dis_state": 1, + "err_code": 0, + "force_upgrade": false, + "message": "0%, 0B/s", + "module": "", + "new_version_state": 1, + "cur_state_code": 0, + "idx2": 3541206801, + "new_ver_list": [ + { + "name": "ota", + "cur_ver": "01.04.00.00", + "new_ver": "01.07.01.00", + "cur_release_type": 3, + "new_release_type": 3 + } + ] + }, + "hms": [], + "online": { + "ahb": false, + "rfid": false, + "version": 1181382388 + }, + "ams": { + "ams": [], + "ams_exist_bits": "0", + "tray_exist_bits": "0", + "tray_is_bbl_bits": "0", + "tray_tar": "255", + "tray_now": "255", + "tray_pre": "255", + "tray_read_done_bits": "0", + "tray_reading_bits": "0", + "version": 5, + "insert_flag": true, + "power_on_flag": true + }, + "xcam": { + "buildplate_marker_detector": true + }, + "vt_tray": { + "id": "254", + "tag_uid": "0000000000000000", + "tray_id_name": "", + "tray_info_idx": "GFG99", + "tray_type": "PETG", + "tray_sub_brands": "", + "tray_color": "000000FF", + "tray_weight": "0", + "tray_diameter": "0.00", + "tray_temp": "0", + "tray_time": "0", + "bed_temp_type": "0", + "bed_temp": "0", + "nozzle_temp_max": "270", + "nozzle_temp_min": "220", + "xcam_info": "000000000000000000000000", + "tray_uuid": "00000000000000000000000000000000", + "remain": 0, + "k": 0.019999999552965164, + "n": 1, + "cali_idx": -1 + }, + "lights_report": [ + { + "node": "chamber_light", + "mode": "on" + } + ], + "command": "push_status", + "msg": 0, + "sequence_id": "1854" + } +} \ No newline at end of file diff --git "a/doc/\347\274\226\350\257\221\346\225\231\347\250\213.md" "b/doc/\347\274\226\350\257\221\346\225\231\347\250\213.md" index 9757c64..a65f2cd 100644 --- "a/doc/\347\274\226\350\257\221\346\225\231\347\250\213.md" +++ "b/doc/\347\274\226\350\257\221\346\225\231\347\250\213.md" @@ -22,6 +22,26 @@ idf.py build ``` 首次编译耗时会比较长 + + **可选:指定电机通道配置** + + 如果需要编译特定的电机通道版本,可以通过 CMake 参数指定: + + - 编译 4 通道版本: + ```bash + idf.py build -DMOTOR_CONFIG=MOTORS_4 + ``` + + - 编译 6 通道版本: + ```bash + idf.py build -DMOTOR_CONFIG=MOTORS_6 + ``` + + - 编译 8 通道版本(默认): + ```bash + idf.py build -DMOTOR_CONFIG=MOTORS_8 + ``` + 或直接使用 `idf.py build`(不传参数默认为 8 通道) 7. 连接设备并烧录 ```bash idf.py -p PORT flash diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ded3a4a..95aa06e 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -11,11 +11,42 @@ idf_component_register( # "../managed_components/s00500_ESPUI/src" ) +# 根据 MOTOR_CONFIG 变量定义相应的宏 +if(DEFINED MOTOR_CONFIG) + if(MOTOR_CONFIG STREQUAL "MOTORS_4") + target_compile_definitions(${COMPONENT_LIB} PRIVATE MOTORS_4) + message(STATUS "Building with MOTORS_4 configuration") + elseif(MOTOR_CONFIG STREQUAL "MOTORS_6") + target_compile_definitions(${COMPONENT_LIB} PRIVATE MOTORS_6) + message(STATUS "Building with MOTORS_6 configuration") + elseif(MOTOR_CONFIG STREQUAL "MOTORS_8") + # MOTORS_8 不定义任何宏,使用默认配置 + message(STATUS "Building with MOTORS_8 configuration (default)") + else() + message(WARNING "Unknown MOTOR_CONFIG value: ${MOTOR_CONFIG}, using default (MOTORS_8)") + endif() +else() + # 如果没有指定 MOTOR_CONFIG,使用默认的 MOTORS_8 配置 + message(STATUS "No MOTOR_CONFIG specified, using default (MOTORS_8)") +endif() + +# 确定通道数量 +if(DEFINED MOTOR_CONFIG) + if(MOTOR_CONFIG STREQUAL "MOTORS_4") + set(CHANNEL_COUNT 4) + elseif(MOTOR_CONFIG STREQUAL "MOTORS_6") + set(CHANNEL_COUNT 6) + else() + set(CHANNEL_COUNT 8) # MOTORS_8 或默认 + endif() +else() + set(CHANNEL_COUNT 8) # 默认8通道 +endif() # 添加自定义目标执行Python脚本 add_custom_target( run_helloworld ALL # ALL表示包含在默认构建目标中 - COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH= python ${CMAKE_CURRENT_SOURCE_DIR}/convert_html_to_hpp.py + COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH= MOTOR_CONFIG=${MOTOR_CONFIG} CHANNEL_COUNT=${CHANNEL_COUNT} python ${CMAKE_CURRENT_SOURCE_DIR}/convert_html_to_hpp.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} # 设置工作目录为脚本所在位置 - COMMENT "RUN convert_html_to_hpp.py..." # 可选:添加构建时的提示信息 + COMMENT "RUN convert_html_to_hpp.py with CHANNEL_COUNT=${CHANNEL_COUNT}..." # 可选:添加构建时的提示信息 ) \ No newline at end of file diff --git a/main/config.hpp b/main/config.hpp index 3a5f68f..4b5356e 100644 --- a/main/config.hpp +++ b/main/config.hpp @@ -7,7 +7,9 @@ #include "web_sync.hpp" -// #define MOTORS_6// 使用当前配置(6通道)注释掉就是8通道 +// #define MOTORS_4 // 使用4通道配置 +// #define MOTORS_6 // 使用6通道配置 +// 默认8通道配置 // #define LOCAL_CONFIG namespace config { @@ -45,7 +47,9 @@ namespace config { //**********************用户配置区开始****************************** -#ifdef MOTORS_6 +#ifdef MOTORS_4 + inline gpio_num_t forward_click = GPIO_NUM_7;//进料微动 +#elif defined(MOTORS_6) inline gpio_num_t forward_click = GPIO_NUM_4;//进料微动 #else inline gpio_num_t forward_click = GPIO_NUM_7; @@ -60,7 +64,17 @@ namespace config { inline std::array motors{ -#ifdef MOTORS_6 +#ifdef MOTORS_4 + // 4通道配置 + motor{1, GPIO_NUM_2, GPIO_NUM_3},// 通道1 + motor{2, GPIO_NUM_10, GPIO_NUM_6},// 通道2 + motor{3, GPIO_NUM_5, GPIO_NUM_4},// 通道3 + motor{4, GPIO_NUM_8, GPIO_NUM_9},// 通道4 + motor{5, GPIO_NUM_NC, GPIO_NUM_NC},// 通道5(未使用) + motor{6, GPIO_NUM_NC, GPIO_NUM_NC},// 通道6(未使用) + motor{7, GPIO_NUM_NC, GPIO_NUM_NC},// 通道7(未使用) + motor{8, GPIO_NUM_NC, GPIO_NUM_NC}// 通道8(未使用) +#elif defined(MOTORS_6) // 当前配置(6通道) motor{1, GPIO_NUM_1, GPIO_NUM_0},// 通道1 motor{2, GPIO_NUM_19, GPIO_NUM_18},// 通道2 @@ -97,6 +111,7 @@ namespace config { mesp::wsStoreValue bambu_ip("bambu_ip", "192.168.1.1"); mesp::wsStoreValue MQTT_pass("MQTT_pass", ""); mesp::wsStoreValue device_serial("device_serial", ""); + mesp::wsStoreValue assist_feeding_enabled("assist_feeding_enabled", 1);// 辅助进料开关,1=开启,0=关闭 //**********************用户配置区结束****************************** //**********************用户配置区结束****************************** diff --git a/main/convert_html_to_hpp.py b/main/convert_html_to_hpp.py index 701cf6b..b10f5fd 100644 --- a/main/convert_html_to_hpp.py +++ b/main/convert_html_to_hpp.py @@ -1,11 +1,13 @@ """ convert_html_to_hpp.py 功能:将HTML文件转换为HPP头文件,并添加自定义头尾注释 +根据MOTOR_CONFIG环境变量动态调整通道数量 """ import argparse +import os -def convert_html_to_hpp(input_file, output_file, header_str="", footer_str=""): +def convert_html_to_hpp(input_file, output_file, header_str="", footer_str="", channel_count=8): try: # 读取并处理HTML内容 processed_lines = [] @@ -31,6 +33,9 @@ def convert_html_to_hpp(input_file, output_file, header_str="", footer_str=""): # 将所有行合并为单个字符串(无换行符) compressed_html = ''.join(processed_lines) + # 替换通道数量占位符 + compressed_html = compressed_html.replace('CHANNEL_COUNT_PLACEHOLDER', str(channel_count)) + # 拼接新内容 new_content = f"{header_str}\n{compressed_html}\n{footer_str}" @@ -38,12 +43,15 @@ def convert_html_to_hpp(input_file, output_file, header_str="", footer_str=""): with open(output_file, 'w', encoding='utf-8') as f: f.write(new_content) - print(f"转换成功!生成文件: {output_file}") + print(f"转换成功!生成文件: {output_file} (通道数: {channel_count})") except Exception as e: print(f"错误: {str(e)}") if __name__ == "__main__": + # 从环境变量获取通道数量,默认为8 + channel_count = int(os.environ.get('CHANNEL_COUNT', '8')) + parser = argparse.ArgumentParser(description="HTML转HPP工具") parser.add_argument('-i', '--input', default='index.html', help='输入文件路径') parser.add_argument('-o', '--output', default='index.hpp', help='输出文件路径') @@ -58,12 +66,16 @@ def convert_html_to_hpp(input_file, output_file, header_str="", footer_str=""): ')rawliteral";', help='尾部自定义字符串') + parser.add_argument('--channels', type=int, default=channel_count, + help=f'通道数量 (默认: {channel_count}, 从环境变量CHANNEL_COUNT读取)') + args = parser.parse_args() convert_html_to_hpp( input_file=args.input, output_file=args.output, header_str=args.header, - footer_str=args.footer + footer_str=args.footer, + channel_count=args.channels ) diff --git a/main/espMQTT.hpp b/main/espMQTT.hpp index 450439b..16eb95e 100644 --- a/main/espMQTT.hpp +++ b/main/espMQTT.hpp @@ -29,20 +29,38 @@ namespace mesp { switch (esp_mqtt_event_id_t(event_id)) { case MQTT_EVENT_CONNECTED: - fpr("MQTT_EVENT_CONNECTED(MQTT连接成功)"); + if (This.state == mqtt_state::disconnected || This.state == mqtt_state::error) { + fpr("MQTT_EVENT_CONNECTED(MQTT重连成功)"); + } else { + fpr("MQTT_EVENT_CONNECTED(MQTT连接成功)"); + } + fpr("MQTT会话已建立"); This.state = mqtt_state::connected; This.state.notify_all(); break; case MQTT_EVENT_DISCONNECTED: fpr("MQTT_EVENT_DISCONNECTED(MQTT断开连接)"); + if (event->error_handle) { + fpr("断开原因 - 错误类型: ", event->error_handle->error_type); + if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { + fpr("TCP传输错误,将自动重连"); + } else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) { + fpr("连接被拒绝,返回码: ", event->error_handle->connect_return_code); + } + } else { + fpr("正常断开或网络异常,将自动重连"); + } This.state = mqtt_state::disconnected; This.state.notify_all(); break; case MQTT_EVENT_BEFORE_CONNECT: - fpr("Mqtt连接前"); + fpr("MQTT_EVENT_BEFORE_CONNECT(MQTT连接前,正在尝试连接...)"); break; case MQTT_EVENT_SUBSCRIBED: fpr("MQTT_EVENT_SUBSCRIBED(MQTT订阅成功),msg_id=", event->msg_id); + if (event->topic_len > 0) { + fpr("订阅主题: ", string(event->topic, event->topic_len)); + } break; case MQTT_EVENT_UNSUBSCRIBED: fpr("MQTT_EVENT_UNSUBSCRIBED(MQTT取消订阅成功),msg_id=", event->msg_id); @@ -51,29 +69,35 @@ namespace mesp { fpr("MQTT_EVENT_PUBLISHED(MQTT消息发布成功),msg_id=", event->msg_id); break; case MQTT_EVENT_DATA: - //fpr("MQTT_EVENT_DATA(接收到MQTT消息)"); - //printf("主题=%.*s\r\n",event->topic_len,event->topic); - printf("%.*s\r\n", event->data_len, event->data); + // fpr("MQTT_EVENT_DATA(接收到MQTT消息)"); + if (event->topic_len > 0) { + fpr("收到消息,主题: ", string(event->topic, event->topic_len), ", 数据长度: ", event->data_len); + } + fpr(string(event->data, event->data_len)); time_out = 0; - f(client, string(event->data)); + f(client, string(event->data, event->data_len)); break; case MQTT_EVENT_ERROR: fpr("MQTT_EVENT_ERROR(MQTT事件错误)"); - if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { - fpr("从esp-tls报告的最后错误代码:", event->error_handle->esp_tls_last_esp_err); - fpr("TLS堆栈最后错误号:", event->error_handle->esp_tls_stack_err); - fpr("最后捕获的errno:", event->error_handle->esp_transport_sock_errno, - strerror(event->error_handle->esp_transport_sock_errno)); - } else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) { - fpr("连接被拒绝错误:", event->error_handle->connect_return_code); - } else { - fpr("未知的错误类型:", event->error_handle->error_type); + if (event->error_handle) { + if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { + fpr("TCP传输错误 - esp-tls错误代码:", event->error_handle->esp_tls_last_esp_err); + fpr("TLS堆栈错误号:", event->error_handle->esp_tls_stack_err); + fpr("Socket错误:", event->error_handle->esp_transport_sock_errno, + " (", strerror(event->error_handle->esp_transport_sock_errno), ")"); + fpr("将自动尝试重连"); + } else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) { + fpr("连接被拒绝,返回码:", event->error_handle->connect_return_code); + fpr("可能原因:用户名/密码错误、服务器拒绝连接等"); + } else { + fpr("未知的错误类型:", event->error_handle->error_type); + } } This.state = mqtt_state::error; This.state.notify_all(); break; default: - fpr("其他事件id:", event->event_id); + fpr("其他MQTT事件id:", event->event_id); break; } }// mqtt_event_callback @@ -100,14 +124,28 @@ namespace mesp { mqtt_cfg.credentials.username = user.c_str(); mqtt_cfg.credentials.authentication.password = pass.c_str(); - - // 关键优化配置 + // 会话配置 + mqtt_cfg.session.keepalive = 60; // 心跳间隔60秒(Keep-Alive) + mqtt_cfg.session.disable_clean_session = false; // 启用clean session + mqtt_cfg.session.protocol_ver = MQTT_PROTOCOL_V_3_1_1; // 使用MQTT 3.1.1协议 + + // 网络配置 + mqtt_cfg.network.reconnect_timeout_ms = 5000; // 5秒重连超时 + mqtt_cfg.network.timeout_ms = 10000; // 10秒网络超时 + mqtt_cfg.network.disable_auto_reconnect = false; // 启用自动重连(默认启用) + + // 缓冲区配置 mqtt_cfg.buffer.size = 4096; // 增大接收缓冲区 mqtt_cfg.buffer.out_size = 2048; // 发送缓冲区 - mqtt_cfg.network.reconnect_timeout_ms = 5000; // 5秒重连 + + // 任务配置 mqtt_cfg.task.stack_size = 6144; // 增大任务栈 mqtt_cfg.task.priority = 5; // 提高任务优先级 + fpr("初始化MQTT客户端,服务器: ", server); + fpr("Keep-Alive心跳间隔: ", mqtt_cfg.session.keepalive, "秒"); + fpr("自动重连: 启用,重连超时: ", mqtt_cfg.network.reconnect_timeout_ms, "毫秒"); + client = esp_mqtt_client_init(&mqtt_cfg); error_check(esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, Mqttclient::mqtt_event_callback, this), "Mqtt注册事件失败"); error_check(esp_mqtt_client_start(client), "Mqtt初始化失败"); diff --git a/main/index.html b/main/index.html index 11e4f4a..14b7c31 100644 --- a/main/index.html +++ b/main/index.html @@ -366,12 +366,12 @@ .channel-controls { display: flex; + flex-direction: column; gap: 16px; margin-bottom: 24px; background: #f8f9fa; padding: 16px; border-radius: 8px; - align-items: center; } .control-group { @@ -409,6 +409,147 @@ color: var(--secondary-color); } + .channel-warning { + width: 100%; + background: #fff3cd; + border-left: 4px solid var(--warning-color); + padding: 12px 16px; + border-radius: 6px; + color: #856404; + font-size: 14px; + display: flex; + align-items: center; + gap: 10px; + box-sizing: border-box; + } + + .channel-warning i { + color: var(--warning-color); + font-size: 16px; + flex-shrink: 0; + } + + .channel-warning span { + line-height: 1.5; + } + + .status-display { + display: flex; + gap: 16px; + margin-bottom: 16px; + padding: 12px 16px; + background: #f8f9fa; + border-radius: 8px; + flex-wrap: wrap; + } + + .status-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + } + + .status-item label { + color: #7f8c8d; + font-weight: 500; + } + + .status-item .status-value { + padding: 4px 12px; + border-radius: 4px; + font-weight: 600; + background: #fff; + border: 1px solid #e0e0e0; + } + + .status-value.idle { + color: var(--success-color); + } + + .status-value.changing { + color: var(--warning-color); + } + + .status-value.loading { + color: var(--secondary-color); + } + + .status-value.locked { + color: var(--danger-color); + } + + .switch { + position: relative; + display: inline-block; + width: 50px; + height: 24px; + } + + .switch input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .4s; + border-radius: 24px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background-color: white; + transition: .4s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: var(--success-color); + } + + input:checked + .slider:before { + transform: translateX(26px); + } + + .switch-label { + margin-left: 8px; + font-weight: 500; + color: #555; + } + + .btn:disabled, + .action-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; + } + + .motor-running { + animation: pulse 1.5s ease-in-out infinite; + } + + @keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.6; + } + } + .help-text { font-size: 12px; color: #7f8c8d; @@ -542,6 +683,33 @@

系统运行日志

diff --git a/main/main.cpp b/main/main.cpp index 601d25d..de7c9d5 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "esptools.hpp" @@ -30,7 +31,7 @@ TaskHandle_t Task2_handle;//延时调试信息 //分割 int bed_target_temper_max = 0; -mesp::wsStoreValue extruder("extruder", 0);// 1-16, 0表示无耗材 +mesp::wsStoreValue extruder("extruder", 0);// 1-16, 0表示无耗材,默认未设置 int sequence_id = -1; std::atomic print_error = 0;//打印错误代码用于判断自动续料 50364437是A1 50364420是P1 @@ -39,6 +40,11 @@ std::atomic pause_lock{false};// 暂停锁 std::atomic nozzle_target_temper = -1; //std::atomic hw_switch{0};//小绿点, 其实是布尔 +// 系统状态变量,用于前端显示和按钮控制 +mesp::wsValue motor_running("motor_running", 0);// 当前运行的电机通道,0表示无电机运行 +mesp::wsValue operation_status("operation_status", "idle");// 操作状态: "idle", "changing", "loading" +mesp::wsValue system_locked("system_locked", false);// 系统锁定状态 + inline constexpr int 正常 = 0; inline constexpr int 退料完成需要退线 = 260;//A1 //inline constexpr int 退料完成需要退线 = 259;//P1 @@ -56,6 +62,27 @@ inline mstd::channel_lock> async_channel;//异步任务通 inline string last_ws_log = "日志初始化";//可能会有多个webfpr,非线程安全注意,现在单核先不管 +// RAII状态管理类,确保在异常或提前返回时也能清理状态 +struct SystemStateGuard { + bool active; + SystemStateGuard() : active(true) { + system_locked = true; + } + ~SystemStateGuard() { + if (active) { + system_locked = false; + operation_status = "idle"; + pause_lock = false; + } + } + void release() { + active = false; + system_locked = false; + operation_status = "idle"; + pause_lock = false; + } +}; + //@brief WebSocket消息打印 inline void webfpr(AsyncWebSocket& ws, const string& str) { @@ -94,6 +121,11 @@ inline void motor_run(int motor_id, bool fwd, T&& t) { config::LED_R = GPIO_NUM_NC; config::LED_L = GPIO_NUM_NC; }//使用到了通道7,关闭代码中的LED控制 + + // 更新电机运行状态 + motor_running = motor_id + 1; // 恢复为1-based索引 + webfpr(std::string("电机 ") + std::to_string(motor_id + 1) + (fwd ? " 正转" : " 反转")); + if (fwd) { esp::gpio_out(config::motors[motor_id].forward, true); mstd::delay(std::forward(t));// 使用传入的延时 @@ -103,6 +135,9 @@ inline void motor_run(int motor_id, bool fwd, T&& t) { mstd::delay(std::forward(t));// 使用传入的延时 esp::gpio_out(config::motors[motor_id].backward, false); } + + // 电机运行完成,清除状态 + motor_running = 0; }//motor_run @@ -140,43 +175,83 @@ void publish(esp_mqtt_client_handle_t client, const std::string& msg) { //换料 void change_filament(esp_mqtt_client_handle_t client, int old_extruder, int new_extruder) { - - constexpr auto fpr = [](const string& r) { webfpr(ws, r); };//重设一下fpr - - fpr("开始换料"); - // esp::gpio_out(config::LED_R, false); + // 使用RAII确保状态总是被清理 + SystemStateGuard state_guard; + operation_status = "changing"; + webfpr("开始换料"); + + // 查询当前通道使用状态(通过hw_switch_state判断是否有料) + publish(client, bambu::msg::get_status); + mstd::delay(3s);//等待查询结果 + + // 检查当前通道是否在使用中(通过hw_switch_state判断) + bool has_filament = (hw_switch == 1); + + // 如果当前记录的通道就是目标通道,且检测到有料,直接继续,无需换料 + if (old_extruder == new_extruder && has_filament) { + webfpr("当前通道" + std::to_string(new_extruder) + "正在使用中,无需换料"); + extruder = new_extruder;// 确保记录正确 + state_guard.release(); // 提前释放状态 + publish(client, bambu::msg::print_resume);// 暂停恢复 + return; + } + + // 需要换料的情况:退出旧通道 + // 如果old_extruder == 0,说明当前无耗材,直接进料新通道 + if (old_extruder > 0 && old_extruder <= config::motors.size()) { + if (!has_filament) { + // 检测到无料,只需要退出当前通道,不需要退料流程 + webfpr("检测到无料,当前通道" + std::to_string(old_extruder) + "直接退线"); + motor_run(old_extruder, false);// 退线 + } else { + // 检测到有料,需要完整的退料流程 + webfpr("检测到有料,当前通道" + std::to_string(old_extruder) + "执行退料"); + if (config::motors[old_extruder - 1].load_time > 0) { + publish(client, bambu::msg::runGcode( + "M109 S" + std::to_string(config::motors[old_extruder - 1].temper.get_value()) + "\nM620 S255\nT255\nM621 S255\n"));//新的快速退料 + webfpr("发送了退料命令,等待退料完成"); + if (!mstd::atomic_wait_un_timeout(ams_status, 退料完成需要退线, 120s)) { + webfpr("等待退料完成超时,可能打印机未响应"); + return; // RAII会自动清理状态 + } + webfpr("退料完成,需要退线,等待退线完"); + + motor_run(old_extruder, false);// 退线 + + if (!mstd::atomic_wait_un_timeout(ams_status, 退料完成, 30s)) { + webfpr("等待退料完成状态超时,继续执行"); + // 继续执行,不返回,因为退线已完成 + } + } + } + } else if (old_extruder == 0) { + webfpr("当前无耗材,直接进料新通道"); + } + + // 进料新通道 if (config::motors[new_extruder - 1].load_time > 0) {//使用固定时间进料@_@ - fpr("使用固定时间进料"); + webfpr("使用固定时间进料到通道" + std::to_string(new_extruder)); // ws_extruder = std::to_string(old_extruder) + string(" → ") + std::to_string(new_extruder); //ws_extruder不再使用,可以考虑给前端加一个状态表示正在换料@_@ - - // publish(client, bambu::msg::uload); - publish(client, bambu::msg::runGcode( - "M109 S" + std::to_string(config::motors[old_extruder - 1].temper.get_value()) + "\nM620 S255\nT255\nM621 S255\n"));//新的快速退料 - fpr("发送了退料命令,等待退料完成"); - mstd::atomic_wait_un(ams_status, 退料完成需要退线); - fpr("退料完成,需要退线,等待退线完"); - - motor_run(old_extruder, false);// 退线 - - mstd::atomic_wait_un(ams_status, 退料完成);// 应该需要这个wait,打印机或者网络偶尔会卡 - int new_nozzle_temper = config::motors[new_extruder - 1].temper.get_value(); publish(client, bambu::msg::runGcode("M109 S" + std::to_string(new_nozzle_temper))); + auto temp_deadline = std::chrono::steady_clock::now() + 300s; // 5分钟超时 while (nozzle_target_temper.load() < new_nozzle_temper - 5) { + if (std::chrono::steady_clock::now() > temp_deadline) { + webfpr("等待热端温度超时,可能打印机未响应"); + return; // RAII会自动清理状态 + } mstd::delay(500ms);// 等待热端温度达到目标温度 } // mstd::delay(5s);//先5s,时间可能取决于热端到250的速度,一个想法是把拉高热端提前能省点时间,但是比较难控制 //@_@也可以读热端温度,不过如果读==250的话,肯定是挤出机先转,或者可以考虑条件为>240之类 - fpr("进线"); + webfpr("进线"); publish(client, bambu::msg::runGcode("G1 E150 F500"));//旋转热端齿轮辅助进料 mstd::delay(3s);//还是需要延迟,命令落实没这么快 motor_run(new_extruder, true);// 进线 - extruder = new_extruder;//换料完成 - // {//旧的使用进线程序的进料过程 // publish(client,bambu::msg::load); // fpr("发送了料进线命令,等待进线完成"); @@ -190,12 +265,21 @@ void change_filament(esp_mqtt_client_handle_t client, int old_extruder, int new_ // mstd::delay(2s); // } + // 换料完成后再更新extruder,避免在换料过程中被callback_fun读取到新值 + extruder = new_extruder;//换料完成 + webfpr("换料完成: 通道" + std::to_string(new_extruder)); + publish(client, bambu::msg::print_resume);// 暂停恢复 } else {//自动判定进料时间 - fpr("小绿点判定进料"); - fpr("还没写"); + webfpr("小绿点判定进料"); + webfpr("功能未实现,无法完成换料"); + state_guard.release(); // 提前释放状态 + return; } -}// work + + // 正常完成,释放状态 + state_guard.release(); +}// change_filament /* * 似乎外挂托盘的数据也能通过mqtt改动 */ @@ -207,43 +291,79 @@ esp_mqtt_client_handle_t __client; void load_filament(int new_extruder) { // __client;//先用这个,之后解耦出来,应该穿参进来,改好clien的生存期和错误回报就行 + // 检查系统是否被锁定(换料或上料进行中) + if (system_locked.get_value() || pause_lock.load()) { + webfpr("系统正在执行其他操作,请稍后再试"); + return; + } + + // 检查__client是否有效 + if (__client == nullptr) { + webfpr("MQTT客户端未初始化,无法执行上料"); + return; + } + if (!(new_extruder > 0 && new_extruder <= config::motors.size())) { webfpr("不支持的上料通道"); return; } + // 使用RAII确保状态总是被清理 + SystemStateGuard state_guard; + operation_status = "loading"; webfpr("开始进料"); {//新写的N20上料 publish(__client, bambu::msg::get_status);//查询小绿点 mstd::delay(3s);//等待查询结果 - if (hw_switch == 1) {//有料需要退料 - int old_extruder = extruder; + + // 进料前检查状态:通过hw_switch_state判断是否有料 + bool has_filament = (hw_switch == 1); + + if (has_filament) {//有料需要检查通道 + int old_extruder = extruder.get_value(); if (old_extruder == 0) { - webfpr("请设置当前所使用通道,否则无法退料再进料"); + webfpr("检测到有料但未设置当前通道,请先设置当前通道"); + state_guard.release(); // 提前释放状态 return; } if (old_extruder == new_extruder) { - webfpr("当前通道已经是" + std::to_string(new_extruder) + "无需上料"); + // 通道一样,跳过进料 + webfpr("当前通道已经是" + std::to_string(new_extruder) + ",且检测到有料,跳过进料"); + state_guard.release(); // 提前释放状态 return; } - // ws_extruder = std::to_string(old_extruder) + string(" → ") + std::to_string(new_extruder); - // publish(__client, bambu::msg::uload); + // 通道变更,需要先退料 + webfpr("检测到有料且通道变更(" + std::to_string(old_extruder) + " → " + std::to_string(new_extruder) + "),执行退料"); publish(__client, bambu::msg::runGcode( "M109 S" + std::to_string(config::motors[old_extruder - 1].temper.get_value()) + "\nM620 S255\nT255\nM621 S255\n"));//新的快速退料 webfpr("发送了退料命令,等待退料完成"); - mstd::atomic_wait_un(ams_status, 退料完成需要退线); + if (!mstd::atomic_wait_un_timeout(ams_status, 退料完成需要退线, 120s)) { + webfpr("等待退料完成超时,可能打印机未响应"); + return; // RAII会自动清理状态 + } webfpr("退料完成,需要退线,等待退线完"); motor_run(old_extruder, false);// 退线 - mstd::atomic_wait_un(ams_status, 退料完成);// 应该需要这个wait,打印机或者网络偶尔会卡 + if (!mstd::atomic_wait_un_timeout(ams_status, 退料完成, 30s)) { + webfpr("等待退料完成状态超时,继续执行"); + // 继续执行,不返回,因为退线已完成 + } webfpr("退线完成"); - }//if (hw_switch == 1) + } else { + // 无料,直接进料 + webfpr("检测到无料,直接进料"); + }//if (has_filament) {//进料 int new_nozzle_temper = config::motors[new_extruder - 1].temper.get_value(); publish(__client, bambu::msg::runGcode("M109 S" + std::to_string(new_nozzle_temper))); + auto temp_deadline = std::chrono::steady_clock::now() + 300s; // 5分钟超时 while (nozzle_target_temper.load() < new_nozzle_temper - 5) { + if (std::chrono::steady_clock::now() > temp_deadline) { + webfpr("等待热端温度超时,可能打印机未响应"); + return; // RAII会自动清理状态 + } mstd::delay(500ms);// 等待热端温度达到目标温度 } // mstd::delay(5s);//先5s,时间可能取决于热端到250的速度,一个想法是把拉高热端提前能省点时间,但是比较难控制 @@ -272,6 +392,9 @@ void load_filament(int new_extruder) { webfpr("上料完成"); }//新写的N20上料 + // 正常完成,释放状态 + state_guard.release(); + return; }//load_filament @@ -283,6 +406,26 @@ void work(mesp::Mqttclient& Mqtt) {//之后应该修改好mesp::Mqttclient生命 __client = Mqtt.client; Mqtt.subscribe(config::topic_subscribe());// 订阅消息 + // 应用启动时检查小绿点状态并初始化通道 + webfpr("检查挤出机状态..."); + publish(__client, bambu::msg::get_status); + mstd::delay(3s);//等待查询结果 + + // 根据hw_switch_state判断当前是否有料 + if (hw_switch == 1) { + // 有料,默认设置为通道1 + if (extruder.get_value() == 0) { + extruder = 1; + webfpr("检测到挤出机有料,默认设置为通道1"); + } else { + webfpr("检测到挤出机有料,当前通道: " + std::to_string(extruder.get_value())); + } + } else { + // 无料,设置为空 + extruder = 0; + webfpr("检测到挤出机无料,当前通道设置为空"); + } + int cnt = 0; while (true) { mstd::delay(20000ms); @@ -320,8 +463,30 @@ void callback_fun(esp_mqtt_client_handle_t client, const std::string& json) {// //@_@这边也有些混乱,实质都是因为打印机网络这边不是很稳定所遗留的写法,需要更好的处理 if (bed_target_temper > 0 && bed_target_temper < 17) {// 读到的温度是通道 if (gcode_state == "PAUSE") { + // 如果正在换料中,忽略新的换料请求,避免重复触发 + if (pause_lock.load() || system_locked.get_value()) { + fpr("换料进行中,忽略新的换料请求"); + return; + } + // mstd::delay(4s);//确保暂停动作(3.5s)完成 // mstd::delay(4500ms);// 貌似4s还是有可能会有bug,貌似bug本质是以前发gcode忘了\n,现在应该不用延时 + + // 保存通道号,避免在恢复热床温度时丢失 + int new_extruder = bed_target_temper; + + // 验证新通道的有效性 + if (new_extruder < 1 || new_extruder > config::motors.size()) { + fpr("无效的通道编号: " + std::to_string(new_extruder)); + if (bed_target_temper_max > 0) { + publish(client, bambu::msg::runGcode(std::string("M190 S") + std::to_string(bed_target_temper_max)));//恢复原来的热床温度 + } + mstd::delay(1000ms); + publish(client, bambu::msg::print_resume); + return; + } + + // 先恢复热床温度(如果bed_target_temper_max > 0) if (bed_target_temper_max > 0) {// 似乎热床置零会导致热端固定到90 publish(client, bambu::msg::runGcode( std::string("M190 S") + std::to_string(bed_target_temper_max)// 恢复原来的热床温度 @@ -330,21 +495,29 @@ void callback_fun(esp_mqtt_client_handle_t client, const std::string& json) {// } int old_extruder = extruder; - int new_extruder = bed_target_temper; + if (old_extruder != new_extruder) {//旧通道不等于新通道 - fpr("唤醒换料程序"); + fpr("唤醒换料程序: 通道" + std::to_string(old_extruder) + " → 通道" + std::to_string(new_extruder)); pause_lock = true; + // 使用捕获值而不是引用,避免bed_target_temper被修改影响 async_channel.emplace([=]() { change_filament(client, old_extruder, new_extruder); }); - } else if (!pause_lock.load()) {// 可能会收到旧消息 - fpr("同一耗材,无需换料"); - publish(client, bambu::msg::runGcode(std::string("M190 S") + std::to_string(bed_target_temper_max)));//恢复原来的热床温度 - mstd::delay(1000ms);//确保暂停动作完成 - publish(client, bambu::msg::print_resume);// 无须换料 + } else {// 同一通道,无需换料 + if (!pause_lock.load()) {// 可能会收到旧消息 + fpr("同一耗材,无需换料"); + if (bed_target_temper_max > 0) { + publish(client, bambu::msg::runGcode(std::string("M190 S") + std::to_string(bed_target_temper_max)));//恢复原来的热床温度 + } + mstd::delay(1000ms);//确保暂停动作完成 + publish(client, bambu::msg::print_resume);// 无须换料 + } else { + // pause_lock已设置,说明换料正在进行中,忽略此消息 + fpr("换料进行中,忽略重复消息"); + } } - if (bed_target_temper_max > 0) - bed_target_temper = bed_target_temper_max;// 必要,恢复温度后,MQTT的更新可能不及时 + // 注意:不要在换料过程中恢复bed_target_temper,避免影响换料流程 + // 换料完成后,bed_target_temper会自然恢复为bed_target_temper_max } else { // publish(client,bambu::msg::get_status);//从第二次暂停开始,PAUSE就不会出现在常态消息里,不知道怎么回事 @@ -378,9 +551,18 @@ void Task1(void* param) { int level = gpio_get_level(config::forward_click); if (level == 0) { - int now_extruder = extruder; - webfpr("微动触发"); - motor_run(now_extruder, true, 1s);// 进线 + // 检查辅助进料开关是否开启 + if (config::assist_feeding_enabled.get_value() == 1) { + int now_extruder = extruder.get_value(); + if (now_extruder > 0 && now_extruder <= config::motors.size()) { + webfpr("微动触发,辅助进料"); + motor_run(now_extruder, true, 1s);// 进线 + } else { + webfpr("微动触发,但当前无有效通道"); + } + } else { + webfpr("微动触发,但辅助进料已关闭"); + } } mstd::delay(50ms); diff --git a/main/tools.hpp b/main/tools.hpp index 0fbf059..64c6252 100644 --- a/main/tools.hpp +++ b/main/tools.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include // #define NO_DEBUG #ifndef NO_DEBUG @@ -63,6 +64,24 @@ namespace mstd { } } + // 带超时的atomic_wait_un,防止永久等待导致死锁 + template + bool atomic_wait_un_timeout(std::atomic& value, T target, const std::chrono::duration& timeout) { + auto deadline = std::chrono::steady_clock::now() + timeout; + auto old_value = value.load(); + while (old_value != target) { + // 检查是否已经超时 + if (std::chrono::steady_clock::now() >= deadline) { + return false; // 超时 + } + // 由于std::atomic::wait不支持超时,使用轮询方式 + // 使用较短的睡眠间隔来定期检查值和超时,避免过度消耗CPU + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + old_value = value.load(); + } + return true; // 成功等待到目标值 + } + }// mstd using mstd::fpr; diff --git a/main/web_sync.hpp b/main/web_sync.hpp index 59c59ab..8fd8cb9 100644 --- a/main/web_sync.hpp +++ b/main/web_sync.hpp @@ -92,7 +92,8 @@ namespace mesp { JsonArray data = doc["data"].as(); JsonObject item = data.createNestedObject(); item["name"] = name; - item["value"] = get_value(); + value_type val = get_value(); + item["value"] = val; } //构造对应的json且发送 diff --git a/openspec/AGENTS.md b/openspec/AGENTS.md new file mode 100644 index 0000000..c2e228c --- /dev/null +++ b/openspec/AGENTS.md @@ -0,0 +1,455 @@ +# OpenSpec 助手指令 + +使用OpenSpec进行规范驱动开发的AI编程助手指令。 + +## TL;DR 快速检查清单 + +- 搜索现有工作:`openspec spec list --long`、`openspec list`(仅在全文搜索时使用 `rg`) +- 决定范围:新功能 vs 修改现有功能 +- 选择唯一的`change-id`:kebab-case格式,动词引导(`add-`、`update-`、`remove-`、`refactor-`) +- 创建脚手架:`proposal.md`、`tasks.md`、`design.md`(仅在需要时)以及按影响功能的增量规范 +- 编写增量:使用`## ADDED|MODIFIED|REMOVED|RENAMED Requirements`;每个需求至少包含一个`#### Scenario:` +- 验证:`openspec validate [change-id] --strict`并修复问题 +- 请求批准:在提案被批准前不要开始实施 + +## 三阶段工作流程 + +### 第一阶段:创建变更 +在需要时创建提案: +- 添加功能或特性 +- 进行破坏性变更(API、模式) +- 改变架构或模式 +- 优化性能(改变行为) +- 更新安全模式 + +触发器(示例): +- "帮我创建一个变更提案" +- "帮我规划一个变更" +- "帮我创建一个提案" +- "我想创建一个规范提案" +- "我想创建一个规范" + +宽松匹配指导: +- 包含以下之一:`proposal`、`change`、`spec` +- 以及以下之一:`create`、`plan`、`make`、`start`、`help` + +跳过提案的情况: +- Bug修复(恢复预期行为) +- 拼写错误、格式、注释 +- 依赖更新(非破坏性) +- 配置变更 +- 现有行为的测试 + +**工作流程** +1. 查看`openspec/project.md`、`openspec list`和`openspec list --specs`了解当前上下文。 +2. 选择唯一的动词引导`change-id`,并在`openspec/changes//`下创建`proposal.md`、`tasks.md`、可选的`design.md`和规范增量的脚手架。 +3. 使用`## ADDED|MODIFIED|REMOVED Requirements`起草规范增量,每个需求至少包含一个`#### Scenario:`。 +4. 运行`openspec validate --strict`并在共享提案前解决任何问题。 + +### 第二阶段:实施变更 +将这些步骤作为TODOs逐一跟踪并完成。 +1. **读取proposal.md** - 了解要构建什么 +2. **读取design.md**(如果存在) - 审查技术决策 +3. **读取tasks.md** - 获取实施检查清单 +4. **按顺序实施任务** - 按顺序完成 +5. **确认完成** - 在更新状态前确保`tasks.md`中的每个项目都已完成 +6. **更新检查清单** - 所有工作完成后,将每个任务设置为`- [x]`,使列表反映实际情况 +7. **批准门槛** - 在提案被审查和批准前不要开始实施 + +### 第三阶段:归档变更 +部署后,创建单独的PR来: +- 将`changes/[name]/`移动到`changes/archive/YYYY-MM-DD-[name]/` +- 如果功能发生变化,更新`specs/` +- 对于仅工具变更,使用`openspec archive --skip-specs --yes`(始终明确传递变更ID) +- 运行`openspec validate --strict`确认归档的变更通过检查 + +## 任何任务之前 + +**上下文检查清单:** +- [ ] 阅读`specs/[capability]/spec.md`中的相关规范 +- [ ] 检查`changes/`中的待处理变更是否存在冲突 +- [ ] 阅读`openspec/project.md`了解约定 +- [ ] 运行`openspec list`查看活跃变更 +- [ ] 运行`openspec list --specs`查看现有功能 + +**创建规范之前:** +- 始终检查功能是否已存在 +- 优先修改现有规范而非创建重复项 +- 使用`openspec show [spec]`查看当前状态 +- 如果请求不明确,在创建脚手架前提出1-2个澄清问题 + +### 搜索指导 +- 枚举规范:`openspec spec list --long`(或脚本使用`--json`) +- 枚举变更:`openspec list`(或`openspec change list --json` - 已弃用但可用) +- 显示详情: + - 规范:`openspec show --type spec`(使用`--json`进行过滤) + - 变更:`openspec show --json --deltas-only` +- 全文搜索(使用ripgrep):`rg -n "Requirement:|Scenario:" openspec/specs` + +## 快速开始 + +### CLI 命令 + +```bash +# 基本命令 +openspec list # 列出活跃变更 +openspec list --specs # 列出规范 +openspec show [item] # 显示变更或规范 +openspec validate [item] # 验证变更或规范 +openspec archive [--yes|-y] # 部署后归档(非交互运行添加--yes) + +# 项目管理 +openspec init [path] # 初始化OpenSpec +openspec update [path] # 更新指令文件 + +# 交互模式 +openspec show # 提示选择 +openspec validate # 批量验证模式 + +# 调试 +openspec show [change] --json --deltas-only +openspec validate [change] --strict +``` + +### 命令标志 + +- `--json` - 机器可读输出 +- `--type change|spec` - 区分项目类型 +- `--strict` - 综合验证 +- `--no-interactive` - 禁用提示 +- `--skip-specs` - 不更新规范的归档 +- `--yes`/`-y` - 跳过确认提示(非交互归档) + +## 目录结构 + +``` +openspec/ +├── project.md # 项目约定 +├── specs/ # 当前事实 - 已构建的内容 +│ └── [capability]/ # 单一专注功能 +│ ├── spec.md # 需求和场景 +│ └── design.md # 技术模式 +├── changes/ # 提案 - 应该改变什么 +│ ├── [change-name]/ +│ │ ├── proposal.md # 为什么、什么、影响 +│ │ ├── tasks.md # 实施检查清单 +│ │ ├── design.md # 技术决策(可选;见标准) +│ │ └── specs/ # 增量变更 +│ │ └── [capability]/ +│ │ └── spec.md # ADDED/MODIFIED/REMOVED +│ └── archive/ # 已完成变更 +``` + +## 创建变更提案 + +### 决策树 + +``` +新请求? +├─ 恢复规范行为的bug修复? → 直接修复 +├─ 拼写/格式/注释? → 直接修复 +├─ 新功能/能力? → 创建提案 +├─ 破坏性变更? → 创建提案 +├─ 架构变更? → 创建提案 +└─ 不明确? → 创建提案(更安全) +``` + +### 提案结构 + +1. **创建目录:** `changes/[change-id]/`(kebab-case格式,动词引导,唯一) + +2. **编写proposal.md:** +```markdown +# Change: [变更的简要描述] + +## Why +[问题/机会的1-2句话说明] + +## What Changes +- [变更的要点列表] +- [用**BREAKING**标记破坏性变更] + +## Impact +- 影响的规范:[功能列表] +- 影响的代码:[关键文件/系统] +``` + +3. **创建规范增量:** `specs/[capability]/spec.md` +```markdown +## ADDED Requirements +### Requirement: 新功能 +系统 SHALL 提供... + +#### Scenario: 成功案例 +- **WHEN** 用户执行操作 +- **THEN** 预期结果 + +## MODIFIED Requirements +### Requirement: 现有功能 +[完整的修改后需求] + +## REMOVED Requirements +### Requirement: 旧功能 +**Reason**: [删除原因] +**Migration**: [处理方式] +``` +如果影响多个功能,在`changes/[change-id]/specs//spec.md`下创建多个增量文件——每个功能一个。 + +4. **创建tasks.md:** +```markdown +## 1. 实施 +- [ ] 1.1 创建数据库模式 +- [ ] 1.2 实现API端点 +- [ ] 1.3 添加前端组件 +- [ ] 1.4 编写测试 +``` + +5. **需要时创建design.md:** +如果适用以下任何情况则创建`design.md`;否则省略: +- 跨领域变更(多个服务/模块)或新架构模式 +- 新的外部依赖或重要数据模型变更 +- 安全、性能或迁移复杂性 +- 编码前从技术决策中受益的模糊性 + +最小`design.md`骨架: +```markdown +## Context +[背景、约束、利益相关者] + +## Goals / Non-Goals +- Goals: [...] +- Non-Goals: [...] + +## Decisions +- Decision: [什么和为什么] +- Alternatives considered: [选项 + 理由] + +## Risks / Trade-offs +- [Risk] → 缓解措施 + +## Migration Plan +[步骤、回滚] + +## Open Questions +- [...] +``` + +## 规范文件格式 + +### 关键:场景格式 + +**正确**(使用####标题): +```markdown +#### Scenario: 用户登录成功 +- **WHEN** 提供有效凭据 +- **THEN** 返回JWT令牌 +``` + +**错误**(不要使用要点或粗体): +```markdown +- **Scenario: 用户登录** ❌ +**Scenario**: 用户登录 ❌ +### Scenario: 用户登录 ❌ +``` + +每个需求必须至少有一个场景。 + +### 需求措辞 +- 对规范性需求使用 SHALL/MUST(除非故意非规范性,否则避免 should/may) + +### 增量操作 + +- `## ADDED Requirements` - 新功能 +- `## MODIFIED Requirements` - 改变的行为 +- `## REMOVED Requirements` - 已弃用功能 +- `## RENAMED Requirements` - 名称变更 + +标题使用`trim(header)`匹配 - 忽略空白。 + +#### 何时使用ADDED vs MODIFIED +- ADDED:引入可以作为需求独立存在的新功能或子功能。当变更是正交的(例如,添加"斜杠命令配置")而不是改变现有需求的语义时,优先使用ADDED。 +- MODIFIED:改变现有需求的行为、范围或验收标准。始终粘贴完整的更新后需求内容(标题 + 所有场景)。归档器将用您在此处提供的内容替换整个需求;部分增量将丢失之前的详细信息。 + +常见陷阱:使用MODIFIED添加新关注点而不包含之前的文本。这会在归档时丢失详细信息。如果您没有明确更改现有需求,而是在ADDED下添加新需求。 + +正确编写MODIFIED需求: +1) 在`openspec/specs//spec.md`中定位现有需求。 +2) 复制整个需求块(从`### Requirement: ...`到其场景)。 +3) 将其粘贴到`## MODIFIED Requirements`下并编辑以反映新行为。 +4) 确保标题文本完全匹配(空白不敏感)并保持至少一个`#### Scenario:`。 + +RENAMED示例: +```markdown +## RENAMED Requirements +- FROM: `### Requirement: Login` +- TO: `### Requirement: User Authentication` +``` + +## 故障排除 + +### 常见错误 + +**"Change must have at least one delta"** +- 检查`changes/[name]/specs/`存在且包含.md文件 +- 验证文件具有操作前缀(## ADDED Requirements) + +**"Requirement must have at least one scenario"** +- 检查场景使用`#### Scenario:`格式(4个井号) +- 不要对场景标题使用要点或粗体 + +**静默场景解析失败** +- 需要精确格式:`#### Scenario: Name` +- 调试:`openspec show [change] --json --deltas-only` + +### 验证技巧 + +```bash +# 始终使用严格模式进行综合检查 +openspec validate [change] --strict + +# 调试增量解析 +openspec show [change] --json | jq '.deltas' + +# 检查特定需求 +openspec show [spec] --json -r 1 +``` + +## 快速路径脚本 + +```bash +# 1) 探索当前状态 +openspec spec list --long +openspec list +# 可选全文搜索: +# rg -n "Requirement:|Scenario:" openspec/specs +# rg -n "^#|Requirement:" openspec/changes + +# 2) 选择变更id并创建脚手架 +CHANGE=add-two-factor-auth +mkdir -p openspec/changes/$CHANGE/{specs/auth} # 展开为: openspec/changes/$CHANGE/specs/auth +printf "## Why\n...\n\n## What Changes\n- ...\n\n## Impact\n- ...\n" > openspec/changes/$CHANGE/proposal.md +printf "## 1. Implementation\n- [ ] 1.1 ...\n" > openspec/changes/$CHANGE/tasks.md + +# 3) 添加增量(示例) +cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF' +## ADDED Requirements +### Requirement: 双重认证 +用户在登录期间 MUST 提供第二个因素。 + +#### Scenario: 需要OTP +- **WHEN** 提供有效凭据 +- **THEN** 需要OTP挑战 +EOF + +# 4) 验证 +openspec validate $CHANGE --strict +``` + +## 多功能示例 + +``` +openspec/changes/add-2fa-notify/ +├── proposal.md +├── tasks.md +└── specs/ + ├── auth/ + │ └── spec.md # ADDED: 双重认证 + └── notifications/ + └── spec.md # ADDED: OTP邮件通知 +``` + +auth/spec.md +```markdown +## ADDED Requirements +### Requirement: 双重认证 +... +``` + +notifications/spec.md +```markdown +## ADDED Requirements +### Requirement: OTP邮件通知 +... +``` + +## 最佳实践 + +### 简单优先 +- 默认<100行新代码 +- 在证明不足之前使用单文件实现 +- 避免没有明确理由的框架 +- 选择无聊的、经过验证的模式 + +### 复杂性触发器 +仅在以下情况添加复杂性: +- 性能数据显示当前解决方案太慢 +- 具体规模要求(>1000用户,>100MB数据) +- 多个经过验证的用例需要抽象 + +### 清晰引用 +- 对代码位置使用`file.ts:42`格式 +- 将规范引用为`specs/auth/spec.md` +- 链接相关变更和PR + +### 功能命名 +- 使用动词-名词:`user-auth`、`payment-capture` +- 每个功能单一用途 +- 10分钟可理解性规则 +- 如果描述需要"AND"则拆分 + +### 变更ID命名 +- 使用kebab-case格式,简短且描述性:`add-two-factor-auth` +- 优先动词引导前缀:`add-`、`update-`、`remove-`、`refactor-` +- 确保唯一性;如果已被占用,附加`-2`、`-3`等 + +## 工具选择指南 + +| 任务 | 工具 | 原因 | +|------|------|-----| +| 按模式查找文件 | Glob | 快速模式匹配 | +| 搜索代码内容 | Grep | 优化的正则搜索 | +| 读取特定文件 | Read | 直接文件访问 | +| 探索未知范围 | Task | 多步骤调查 | + +## 错误恢复 + +### 变更冲突 +1. 运行`openspec list`查看活跃变更 +2. 检查重叠规范 +3. 与变更所有者协调 +4. 考虑组合提案 + +### 验证失败 +1. 使用`--strict`标志运行 +2. 检查JSON输出获取详细信息 +3. 验证规范文件格式 +4. 确保场景格式正确 + +### 缺少上下文 +1. 首先阅读project.md +2. 检查相关规范 +3. 查看最近的归档 +4. 请求澄清 + +## 快速参考 + +### 阶段指示器 +- `changes/` - 已提议,尚未构建 +- `specs/` - 已构建和部署 +- `archive/` - 已完成变更 + +### 文件用途 +- `proposal.md` - 为什么和什么 +- `tasks.md` - 实施步骤 +- `design.md` - 技术决策 +- `spec.md` - 需求和行为 + +### CLI基本命令 +```bash +openspec list # 进行中有什么? +openspec show [item] # 查看详情 +openspec validate --strict # 是否正确? +openspec archive [--yes|-y] # 标记完成(自动化添加--yes) +``` + +记住:规范是事实。变更是提案。保持它们同步。 diff --git a/openspec/project.md b/openspec/project.md new file mode 100644 index 0000000..8f7dc12 --- /dev/null +++ b/openspec/project.md @@ -0,0 +1,164 @@ +# Project Context + +## Purpose +Top-AMS 是拓竹(Bambu Lab)打印机的第三方多色换色系统。项目通过 ESP32-C3 微控制器控制电机实现自动换色功能,追求更好的换色过程、更多的自定义配置以及更实惠的成本。 + +**核心原理**: +- 在拓竹换色 G-code 中插入热床温度改变并暂停 +- 热床温度(1-17)对应不同的耗材通道 +- ESP32 通过 MQTT 订阅得知热床温度变化 +- 使用 MQTT 操作进退料并控制相应电机 +- 最后恢复暂停继续打印 + +**支持范围**: +- 目前支持 PCB 设计最多 8 个通道,理论无通道数量上限 +- 兼容机型:A1 mini、A1、P1S(固件版本 1.04.00 及以上) + +## Tech Stack +- **编程语言**: C++20 +- **开发框架**: ESP-IDF (>=4.1.0) +- **硬件平台**: ESP32-C3 微控制器(合宙开发板) +- **Arduino 兼容层**: espressif/arduino-esp32 (^3.1.3) +- **网络通信**: + - MQTT (MQTTS) - 与打印机通信 + - AsyncWebServer - Web 管理界面 + - WebSocket - 实时状态同步 +- **JSON 处理**: bblanchon/arduinojson (^7.3.1) +- **构建系统**: CMake +- **Web 界面**: HTML/CSS/JavaScript(编译时转换为 C++ 头文件) + +## Project Conventions + +### Code Style +- **命名空间**: 使用命名空间组织模块(`config`、`mesp`、`mstd`、`esp`) +- **命名约定**: + - 变量和函数使用小写字母和下划线(snake_case) + - 结构体和类使用小写字母(如 `motor`、`SystemStateGuard`) + - 常量使用中文或英文描述性名称 +- **注释风格**: + - 支持中英文注释 + - 使用 `//` 单行注释和 `/* */` 多行注释 + - 关键功能使用 `@brief` 文档注释 +- **代码组织**: + - 头文件使用 `#pragma once` 防止重复包含 + - 模块化设计,每个功能模块独立头文件 + - 用户配置区域明确标记(`//**********************用户配置区开始******************************`) + +### Architecture Patterns +- **模块化设计**: + - `config.hpp` - 配置管理(电机配置、MQTT 配置等) + - `channel.hpp` - 通道和锁机制 + - `espIO.hpp` - GPIO 和硬件接口 + - `espMQTT.hpp` - MQTT 通信 + - `bambu.hpp` - 打印机协议处理 + - `web_sync.hpp` - WebSocket 数据同步 + - `tools.hpp` - 工具函数 +- **RAII 资源管理**: + - 使用 RAII 模式管理状态(如 `SystemStateGuard`) + - 使用智能指针和 RAII 锁管理资源 +- **异步任务处理**: + - 使用 FreeRTOS 任务处理并发操作 + - 使用信号量和通道进行任务间通信 +- **配置驱动**: + - 通过编译时宏定义支持不同通道配置(MOTORS_4、MOTORS_6、MOTORS_8) + - 运行时配置通过 Web 界面和持久化存储管理 + +### Testing Strategy +- **当前状态**: 项目主要依赖实际硬件测试和用户反馈 +- **测试方法**: + - 硬件在环测试(HIL) + - 实际打印机集成测试 + - Web 界面功能验证 +- **验证点**: + - MQTT 连接和消息处理 + - 电机控制准确性 + - 换色流程完整性 + - Web 界面响应性 + +### Git Workflow +- **分支策略**: 主分支用于稳定版本,功能开发使用特性分支 +- **提交规范**: + - 使用中文或英文提交信息 + - 提交信息应清晰描述变更内容 +- **版本管理**: 通过 RELEASE_NOTES.md 记录版本变更 + +## Domain Context +### 3D 打印术语 +- **AMS (Automatic Material System)**: 自动材料系统,拓竹打印机的多色打印系统 +- **G-code**: 3D 打印机控制指令 +- **热床温度**: 用于在 1-17 范围内传递通道信息(避免与正常打印温度冲突) +- **回抽 (Retraction)**: 打印头后退以停止挤出 +- **冲刷体积 (Purge Volume)**: 换色时用于清理旧颜色的材料体积 + +### 硬件术语 +- **GPIO**: 通用输入输出引脚 +- **微动开关**: 用于检测进料位置的硬件开关 +- **N20/TT 电机**: 不同型号的步进电机方案 +- **通道**: 每个耗材颜色对应的独立进料通道 + +### 系统状态 +- **操作状态**: `idle`(空闲)、`changing`(换色中)、`loading`(进料中) +- **系统锁定**: 防止在换色过程中进行其他操作 +- **打印错误代码**: + - `0` - 正常 + - `260` (A1) / `259` (P1) - 退料完成需要退线 + - `262` - 进料检查 + - `263` - 进料冲刷 + - `768` - 进料完成 + +### 工作流程 +1. **换色触发**: 打印机通过 G-code 设置热床温度(1-17)并暂停 +2. **通道识别**: ESP32 通过 MQTT 订阅获取热床目标温度,映射到对应通道 +3. **退料**: 控制当前通道电机退料,等待打印机完成退料流程 +4. **进料**: 控制目标通道电机进料,等待打印机完成进料和冲刷 +5. **恢复打印**: 解除暂停,继续打印 + +## Important Constraints +### 硬件约束 +- **GPIO 限制**: + - GPIO 18、19 与 USB 冲突(Type-C 充电器可能影响) + - GPIO 12、13 与 LED 灯冲突(使用通道 7 前需激活) +- **通道配置**: 支持 4、6、8 通道配置,通过编译时宏定义选择 +- **内存限制**: ESP32-C3 内存有限,需注意内存使用 + +### 软件约束 +- **固件版本兼容性**: + - 1.04.00 固件版本已验证支持 + - 1.05 固件版本增加了鉴权,当前可能不支持(需测试) +- **MQTT 连接**: + - 需要打印机局域网模式密码(开关状态不影响连接) + - 需要设备序列号进行主题订阅 +- **网络要求**: ESP32 和打印机需在同一局域网 + +### 功能约束 +- **首层换色**: 默认设定为首层不会换色,需确认热端内通道为第一个颜色 +- **料线准备**: 换色前需将待换料的料线推进至能在进料时间内进入热端的距离 +- **温度范围**: 热床温度 1-17 范围用于通道信息传递,避免设置此范围内的热床温度 + +## External Dependencies +### 硬件依赖 +- **主控模块**: 合宙 ESP32-C3 开发板 +- **PCB 板**: 自定义设计的 8 通道控制板(可在 OSHWHub 获取) +- **电机模块**: + - N20 电机方案(项目最早设计) + - TT 电机方案(成本更低) + - N20D 成对电机设计(适配 A1 龙门) + +### 软件依赖 +- **ESP-IDF**: Espressif 官方开发框架(>=4.1.0) +- **Arduino 兼容层**: espressif/arduino-esp32 (^3.1.3) +- **ArduinoJson**: JSON 序列化/反序列化库 (^7.3.1) +- **ESPAsyncWebServer**: 异步 Web 服务器 (^3.7.4) + +### 外部服务 +- **拓竹打印机**: + - MQTT 服务器(MQTTS,端口 8883) + - 设备序列号用于主题订阅 + - 局域网模式密码用于认证 +- **WiFi 网络**: ESP32 需要通过 SmartConfig 或 Web 界面配置 WiFi + +### 开发工具 +- **构建工具**: CMake (>=3.5) +- **Python 脚本**: `convert_html_to_hpp.py` - 将 HTML 转换为 C++ 头文件 +- **固件刷入**: ESP-IDF Flash Download Tool +- **配网工具**: 微信小程序"一键配网"(SmartConfig 协议)