├── .github └── workflows │ ├── contributors.yaml │ ├── esphome.yaml │ ├── pages-version.yaml │ └── pages.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .yamlfmt ├── CHANGELOG.md ├── CNAME ├── LICENSE-Dependencies.txt ├── LICENSE-Documentation.txt ├── LICENSE-Hardware.txt ├── LICENSE-Software.txt ├── LICENSE.txt ├── NOTES.md ├── README.md ├── TODO.md ├── docs ├── .gitignore ├── Gemfile ├── Makefile ├── README.md ├── _config.yml ├── _includes │ ├── footer_custom.html │ └── head_custom.html ├── _plugins │ └── file_size.rb ├── _sass │ └── custom │ │ └── custom.scss ├── assets │ └── js │ │ └── tag-handler.js ├── favicon.ico ├── firmware │ ├── index.md │ ├── manifest.json │ ├── openspool-esp32s2.factory.bin │ ├── openspool-esp32s2.factory.bin.md5 │ ├── openspool-esp32s2.ota.bin │ ├── openspool-esp32s2.ota.bin.md5 │ ├── openspool-esp32s3.factory.bin │ ├── openspool-esp32s3.factory.bin.md5 │ ├── openspool-esp32s3.ota.bin │ └── openspool-esp32s3.ota.bin.md5 ├── hardware │ ├── index.md │ ├── openspool-ams.md │ └── openspool-mini.md ├── images │ ├── BambuLogoLarge.png │ ├── BambuLogoSmall.png │ ├── IMG_5066.heic │ ├── NFC1.png │ ├── NFC3.png │ ├── OpenSpoolAMS1.png │ ├── OpenSpoolLogoMedium1.png │ ├── OpenSpoolMini1.heic │ ├── OpenSpoolMini2.png │ ├── OpenSpoolMini3.png │ ├── OpenSpoolMiniWiringDiagram.png │ ├── OpenSpoolMiniWiringDiagram2.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── phoneNFC.jpeg │ ├── printersettings.png │ ├── site.webmanifest │ └── wifi1.png ├── index.md ├── makerchip.md ├── privacy_policy.md ├── rfid.md └── tag.md ├── extras ├── firmware_upload │ └── batch_upload.sh ├── shipping │ ├── README.md │ └── get_mac.py └── stickers │ ├── .gitignore │ ├── 1-Inch-Template.pdf │ ├── BambuLogo1Inch.pdf │ ├── BambuLogo1Inch.png │ ├── OpenSpoolLogo1Inch.pdf │ ├── OpenSpoolLogo1Inch.png │ ├── README.md │ ├── generate-stickers.py │ └── generate-template.py ├── firmware ├── .gitignore ├── Makefile ├── bambu.h ├── common.yaml ├── components │ ├── nfc │ │ ├── __init__.py │ │ ├── automation.cpp │ │ ├── automation.h │ │ ├── binary_sensor │ │ │ ├── __init__.py │ │ │ ├── binary_sensor.cpp │ │ │ └── binary_sensor.h │ │ ├── nci_core.h │ │ ├── nci_message.cpp │ │ ├── nci_message.h │ │ ├── ndef_message.cpp │ │ ├── ndef_message.h │ │ ├── ndef_record.cpp │ │ ├── ndef_record.h │ │ ├── ndef_record_text.cpp │ │ ├── ndef_record_text.h │ │ ├── ndef_record_uri.cpp │ │ ├── ndef_record_uri.h │ │ ├── nfc.cpp │ │ ├── nfc.h │ │ ├── nfc_helpers.cpp │ │ ├── nfc_helpers.h │ │ ├── nfc_tag.cpp │ │ └── nfc_tag.h │ └── pn532 │ │ ├── __init__.py │ │ ├── binary_sensor.py │ │ ├── pn532.cpp │ │ ├── pn532.h │ │ ├── pn532_mifare_classic.cpp │ │ └── pn532_mifare_ultralight.cpp ├── conf.d │ ├── .gitignore │ ├── api.yaml │ ├── automation.yaml │ ├── bambu_printer.yaml │ ├── button.yaml │ ├── debug.yaml │ ├── extra.yaml │ ├── filament.yaml │ ├── i2c.yaml │ ├── improv-bluetooth.yaml │ ├── improv-serial.yaml │ ├── led-external.yaml │ ├── led-internal.yaml │ ├── logger.yaml │ ├── mqtt_bambu_lan.yaml │ ├── ota.yaml │ ├── pn532_rfid-ams.yaml │ ├── pn532_rfid-solo.yaml │ ├── psram-esp32s2.yaml │ ├── psram-esp32s3.yaml │ ├── status_led.yaml │ ├── time.yaml │ ├── update.yaml │ ├── uptime.yaml │ ├── version.yaml │ ├── web_server.yaml │ └── wifi.yaml ├── esp32-s3-devkitc-1.yaml ├── lolin_s2_mini.yaml ├── lolin_s3_mini.yaml ├── manifest.json ├── openspool-ams.yaml └── openspool-mini.yaml ├── hardware ├── README.md ├── fritzing components │ ├── ESP32-S3-WROOM-1-N16R8-dev-board.fzpz │ ├── PN532 Elechouse RFID NFC Module V3.fzpz │ ├── WS2812 RGB LED strip.fzpz │ ├── Wemos-s2-mini.fzpz │ ├── wago-221-413.fzpz │ └── wago-221-415.fzpz ├── openspool-mini-daughterboard │ ├── v1-color │ │ ├── Gerber_PCB_PCB_OpenSpool-Mini-Daughterboard_2025-01-10_2025-01-10.zip │ │ ├── README.md │ │ ├── Screenshot 2025-01-10 at 20.30.41.png │ │ └── Screenshot 2025-01-10 at 20.30.48.png │ └── v1 │ │ ├── BOM_OpenSpool-Mini-Daughterboard_2025-01-10.csv │ │ ├── EasyEDA.zip │ │ ├── Gerber_OpenSpool-Mini-Daughterboard_PCB_OpenSpool-Mini-Daughterboard_2025-01-10.zip │ │ ├── IMG_6507 Large.jpeg │ │ ├── PickAndPlace_PCB_OpenSpool-Mini-Daughterboard_2025-01-10.csv │ │ ├── README.md │ │ ├── Schematic_OpenSpool-Mini-Daughterboard_2025-01-10.pdf │ │ ├── Screenshot 2025-01-10 at 18.37.22.png │ │ ├── Screenshot 2025-01-10 at 18.37.32.png │ │ └── Screenshot 2025-01-10 at 18.37.39.png ├── openspool-mini │ ├── BOM.csv │ ├── OpenSpool-Mini.fzz │ ├── v1.0 │ │ ├── Gerber_OpenSpool_PCB_OpenSpool_2024-11-05.zip │ │ ├── PCB_PCB_OpenSpool_2024-11-05.json │ │ └── SCH_OpenSpool_2024-11-05.json │ ├── v2.0 │ │ ├── Gerber_OpenSpool_PCB_OpenSpool_2024-11-19.zip │ │ ├── PCB_PCB_OpenSpool_2024-11-19.json │ │ ├── SCH_OpenSpool_2024-11-19.json │ │ ├── Schematic_OpenSpool_2024-11-19.pdf │ │ ├── Screenshot 2024-11-19 at 22.33.28.png │ │ └── Screenshot 2024-11-20 at 10.53.56.png │ ├── v2.1 │ │ ├── Gerber_OpenSpool_PCB_OpenSpool_2024-11-20.zip │ │ ├── PCB_PCB_OpenSpool_2024-11-20.json │ │ ├── SCH_OpenSpool_2024-11-20.json │ │ └── Screenshot 2024-11-20 at 10.53.56.png │ ├── v3.0 │ │ ├── BOM_OpenSpool_2024-12-14.csv │ │ ├── Gerber_OpenSpool_PCB_OpenSpool_2024-12-14.zip │ │ ├── PCB_PCB_OpenSpool_2024-12-14.json │ │ ├── PickAndPlace_PCB_OpenSpool_2024-12-14.csv │ │ ├── SCH_OpenSpool_2024-12-14.json │ │ ├── Schematic_OpenSpool_2024-12-14.pdf │ │ ├── Screenshot 2024-12-14 at 23.07.57.png │ │ └── Screenshot 2024-12-14 at 23.08.03.png │ └── v3.1 │ │ ├── BOM_OpenSpool_2024-12-24.csv │ │ ├── Gerber_OpenSpool_PCB_OpenSpool_2024-12-25.zip │ │ ├── PCB_OpenSpool_2024-12-24.dxf │ │ ├── PCB_PCB_OpenSpool_2024-12-25.json │ │ └── SCH_OpenSpool_2024-12-25.json ├── openspool-pro │ ├── OpenSpool-AMS.fzz │ └── v0.3 │ │ ├── OpenSpool Pro - v0.3 - 250113.f3d │ │ ├── OpenSpool Pro - v0.3 - 250113.pdf │ │ └── Screenshot_2025-01-13_at_10.35.16_PM.png └── pn532 │ ├── PN532_ Manual_V3.pdf │ ├── PN532_AntennaDesign_v1.0.pdf │ ├── PN532_C1.pdf │ └── PN532_shematic_drowing.pdf ├── images ├── BambuLogo1.png ├── Banner.png ├── Change_fillament.png ├── Headers1.png ├── Icon3.png ├── LED1.png ├── LED2.png ├── LLC1.png ├── Logo v11.step ├── Logo.svg ├── Logo.xcf ├── Logo1.svg ├── Logo2.png ├── Logo2.svg ├── Logo3.png ├── Logo4.png ├── NFC.png ├── NFC2.png ├── NFC3.png ├── OpenSpoolLogo.ai.ps ├── OpenSpoolLogo.png ├── OpenSpoolLogo2.png ├── OpenSpoolLogoMedium1.png ├── OpenSpoolLogoSmall1.png ├── OpenSpoolMiniWiringDiagram.png ├── OpenSpoolMiniWiringDiagram2.png ├── PCB1.png ├── PN532-4.jpg ├── PSRAM.png ├── PrinterSettings.png ├── README.md ├── RainbowReel.excalidraw ├── RainbowReel.png ├── Simulator Screenshot - iPhone SE (3rd generation) - 2025-01-03 at 22.12.15.png ├── WebInterface1.png ├── WiringDiagram1.png ├── WiringDiagram2.png ├── certification-mark-US002704-stacked.svg ├── certification-mark-US002704-wide.svg ├── jumper1.png ├── made-for-esphome-white-on-black.svg ├── mqttx.png ├── nfc-bambuhandy.png ├── nfc-openspool.png ├── openspool-transparent.png ├── oshw-logo.svg ├── oshw_facts-2.svg ├── phone2.png ├── pn532-large.png ├── pn532-small.png ├── tindie │ ├── IMG_6108.HEIC │ ├── IMG_6108.png │ ├── IMG_6109.HEIC │ ├── IMG_6113.HEIC │ ├── IMG_6120.HEIC │ ├── IMG_6120.png │ ├── IMG_6125-small.png │ ├── IMG_6125.HEIC │ ├── IMG_6125.jpg │ └── IMG_6125.png ├── wemos-d1mini.png ├── wemos-d1mini2.png.jpg └── wemos-d1minis3.png └── release.sh /.github/workflows/contributors.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Contributors 3 | on: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | contrib-readme-job: 9 | runs-on: ubuntu-latest 10 | name: A job to automate contrib in readme 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | steps: 15 | - name: Contribute List 16 | uses: akhilmhdh/contributors-readme-action@v2.3.10 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/pages-version.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Update Documentation Version 3 | on: 4 | push: 5 | branches: ["main"] 6 | paths: 7 | - 'firmware/conf.d/version.yaml' 8 | jobs: 9 | update-docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | with: 15 | token: ${{ secrets.PAT }} 16 | 17 | - name: Setup yq 18 | uses: vegardit/gha-setup-yq@v1 19 | 20 | - name: Read manifest and update docs 21 | run: | 22 | # Read version from manifest.json 23 | VERSION=$(yq eval '.substitutions.version' firmware/conf.d/version.yaml) 24 | 25 | echo "VERSION=$VERSION" 26 | sed -i 's/badge\/openspool-[^-]*-magenta/badge\/openspool-'"$VERSION"'-magenta/' docs/index.md 27 | sed -i 's/badge\/openspool-[^-]*-magenta/badge\/openspool-'"$VERSION"'-magenta/' README.md 28 | echo "docs/index.md" 29 | cat docs/index.md | grep shields.io 30 | echo "README.md" 31 | cat README.md | grep shields.io 32 | 33 | - name: Commit manifest file 34 | uses: EndBug/add-and-commit@v9 35 | with: 36 | committer_name: GitHub Actions 37 | committer_email: actions@github.com 38 | message: Update manifest.json 39 | add: "docs/*.md README.md" 40 | push: true 41 | -------------------------------------------------------------------------------- /.github/workflows/pages.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This workflow uses actions that are not certified by GitHub. 3 | # They are provided by a third-party and are governed by 4 | # separate terms of service, privacy policy, and support 5 | # documentation. 6 | 7 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 8 | name: OpenSpool.io Docs 9 | on: 10 | push: 11 | branches: ["main"] 12 | paths: 13 | - "docs/**" 14 | - "firmware/manifest.json" 15 | # Allows you to run this workflow manually from the Actions tab 16 | workflow_dispatch: 17 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 18 | permissions: 19 | contents: read 20 | pages: write 21 | id-token: write 22 | # Allow one concurrent deployment 23 | concurrency: 24 | group: "pages" 25 | cancel-in-progress: true 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | defaults: 31 | run: 32 | working-directory: docs 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | - name: Setup Ruby 37 | uses: ruby/setup-ruby@v1 38 | with: 39 | ruby-version: '3.3' # Not needed with a .ruby-version file 40 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 41 | cache-version: 0 # Increment this number if you need to re-download cached gems 42 | working-directory: '${{ github.workspace }}/docs' 43 | - name: Setup Pages 44 | id: pages 45 | uses: actions/configure-pages@v5 46 | - name: Build with Jekyll 47 | # Outputs to the './_site' directory by default 48 | run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" 49 | env: 50 | JEKYLL_ENV: production 51 | - name: Upload artifact 52 | # Automatically uploads an artifact from the './_site' directory by default 53 | uses: actions/upload-pages-artifact@v3 54 | with: 55 | path: "docs/_site/" 56 | # Deployment job 57 | deploy: 58 | environment: 59 | name: github-pages 60 | url: ${{ steps.deployment.outputs.page_url }} 61 | runs-on: ubuntu-latest 62 | needs: build 63 | steps: 64 | - name: Deploy to GitHub Pages 65 | id: deployment 66 | uses: actions/deploy-pages@v4 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | downloaded-artifacts/ 2 | .vscode/ 3 | artifacts/ -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | # yamllint has trouble with scalars like '- lambda: |- ' 4 | # - repo: https://github.com/google/yamlfmt 5 | # rev: v0.10.0 6 | # hooks: 7 | # - id: yamlfmt 8 | - repo: local 9 | hooks: 10 | - id: esphome-openspool-mini 11 | name: ESPHome Validate Solo Config 12 | # entry: bash -c 'podman run -v "$(pwd)/firmware:/data" --rm ghcr.io/esphome/esphome:2024.11.0 config /data/openspool-mini.yaml' 13 | entry: esphome config firmware/openspool-mini.yaml 14 | language: system 15 | pass_filenames: false 16 | files: ^firmware/ 17 | - id: esphome-openspool-ams 18 | name: ESPHome Validate AMS Config 19 | # entry: bash -c 'podman run -v "$(pwd)/firmware:/data" --rm ghcr.io/esphome/esphome:2024.11.0 config /data/openspool-ams.yaml' 20 | entry: esphome config firmware/openspool-ams.yaml 21 | language: system 22 | pass_filenames: false 23 | files: ^firmware/ 24 | -------------------------------------------------------------------------------- /.yamlfmt: -------------------------------------------------------------------------------- 1 | formatter: 2 | indent: 2 3 | line_ending: lf 4 | include_document_start: true 5 | colons: align 6 | scan_folded_as_literal: true 7 | disallow_anchors: false 8 | retain_line_breaks: true -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/CHANGELOG.md -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | openspool.io -------------------------------------------------------------------------------- /LICENSE-Dependencies.txt: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | - esphome 4 | - esp-if 5 | - just-the-docs 6 | - fritzing 7 | - easyeda 8 | - fusion360 -------------------------------------------------------------------------------- /LICENSE-Documentation.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution (CC-BY-4.0) -------------------------------------------------------------------------------- /LICENSE-Hardware.txt: -------------------------------------------------------------------------------- 1 | CERN-OHL-S-2.0 -------------------------------------------------------------------------------- /LICENSE-Software.txt: -------------------------------------------------------------------------------- 1 | Apache License 2.0 -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | # Hardware 3 | 4 | See [./LICENSE-Hardware.txt](./LICENSE-Hardware.txt) 5 | 6 | # Software 7 | 8 | See [./LICENSE-Software.txt](./LICENSE-Software.txt) 9 | 10 | 11 | # Documentation 12 | 13 | See [./LICENSE-Documentation.txt](./LICENSE-Documentation.txt) -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | #PN532 2 | 3 | https://forum.dangerousthings.com/t/success-pn532-is-not-easy-to-work-with/1108 4 | 5 | https://stackoverflow.com/questions/43563378/how-do-i-read-passive-nfc-rfid-units-with-pn532 6 | 7 | Beware some PN532 are clones and don't work as well 8 | 9 | https://forum.dangerousthings.com/t/success-pn532-is-not-easy-to-work-with/1108/10 10 | 11 | 12 | # Filament Mappings 13 | WolfWithSword identified these mappings 14 | ``` 15 | // "func": "// Bambu spools are not needed for translation if they have the rfid tags 16 | // but this can be used to overwrite them for HA displaying only 17 | // This only overwrites the tray_type and tray_sub_brands, if you want other fields overwritten you can add them 18 | // e.g. tray_diameter, tray_weight, temps etc 19 | 20 | var PolyLite_PLA = { 21 | \"tray_info_idx\": \"GFL00\", 22 | \"tray_type\": \"PLA\", 23 | \"tray_sub_brands\": \"PolyLite PLA\" 24 | } 25 | 26 | var PolyTerra_PLA = { 27 | \"tray_info_idx\": \"GFL01\", 28 | \"tray_type\": \"PLA\", 29 | \"tray_sub_brands\": \"PolyTerra PLA\" 30 | } 31 | 32 | var Bambu_ABS = { 33 | \"tray_info_idx\": \"GFB00\", 34 | \"tray_type\": \"ABS\" 35 | } 36 | 37 | var Bambu_PACF = { 38 | \"tray_info_idx\": \"GFN03\", 39 | \"tray_type\": \"PA-CF\" 40 | } 41 | 42 | var Bambu_PC = { 43 | \"tray_info_idx\": \"GFC00\", 44 | \"tray_type\": \"PC\" 45 | } 46 | 47 | var Bambu_PLA_Basic = { 48 | \"tray_info_idx\": \"GFA00\", 49 | \"tray_type\": \"PLA\", 50 | \"tray_sub_brands\": \"PLA Basic\" 51 | } 52 | var Bambu_PLA_Matte = { 53 | \"tray_info_idx\": \"GFA01\", 54 | \"tray_type\": \"PLA\", 55 | \"tray_sub_brands\": \"PLA Matte\" 56 | } 57 | 58 | var Support_G = { 59 | \"tray_info_idx\": \"GFS01\", 60 | \"tray_type\": \"Support\", 61 | \"tray_sub_brands\": \"Support G\" 62 | } 63 | 64 | var Support_W = { 65 | \"tray_info_idx\": \"GFS00\", 66 | \"tray_type\": \"Support\", 67 | \"tray_sub_brands\": \"Support W\" 68 | } 69 | 70 | var Bambu_TPU_95A = { 71 | \"tray_info_idx\": \"GFU01\", 72 | \"tray_type\": \"TPU\", 73 | \"tray_sub_brands\": \"TPU 95A\" 74 | } 75 | 76 | var Generic_ABS = { 77 | \"tray_info_idx\": \"GFB99\", 78 | \"tray_type\": \"ABS\", 79 | \"tray_sub_brands\": \"ABS\" 80 | } 81 | 82 | var Generic_ASA = { 83 | \"tray_info_idx\": \"GFB98\", 84 | \"tray_type\": \"ASA\", 85 | \"tray_sub_brands\": \"ASA\" 86 | } 87 | 88 | var Generic_PA = { 89 | \"tray_info_idx\": \"GFN99\", 90 | \"tray_type\": \"PA\", 91 | \"tray_sub_brands\": \"PA\" 92 | } 93 | 94 | var Generic_PACF = { 95 | \"tray_info_idx\": \"GFN98\", 96 | \"tray_type\": \"PA-CF\", 97 | \"tray_sub_brands\": \"PA-CF\" 98 | } 99 | 100 | var Generic_PC = { 101 | \"tray_info_idx\": \"GFC99\", 102 | \"tray_type\": \"PC\", 103 | \"tray_sub_brands\": \"PC\" 104 | } 105 | 106 | var Generic_PETG = { 107 | \"tray_info_idx\": \"GFG99\", 108 | \"tray_type\": \"PETG\", 109 | \"tray_sub_brands\": \"PETG\" 110 | } 111 | var Generic_PLA = { 112 | \"tray_info_idx\": \"GFL99\", 113 | \"tray_type\": \"PLA\", 114 | \"tray_sub_brands\": \"PLA\" 115 | } 116 | var Generic_PLACF = { 117 | \"tray_info_idx\": \"GFL98\", 118 | \"tray_type\": \"PLA-CF\", 119 | \"tray_sub_brands\": \"PLA-CF\" 120 | } 121 | var Generic_PVA = { 122 | \"tray_info_idx\": \"GFS99\", 123 | \"tray_type\": \"PVA\", 124 | \"tray_sub_brands\": \"PVA\" 125 | } 126 | var Generic_TPU = { 127 | \"tray_info_idx\": \"GFU99\", 128 | \"tray_type\": \"TPU\", 129 | \"tray_sub_brands\": \"TPU\" 130 | } 131 | 132 | var Bambu_PETCF = { 133 | \"tray_info_idx\": \"GFT00\", 134 | \"tray_type\": \"PET-CF\" 135 | } 136 | 137 | var Bambu_PLA_Impact = { 138 | \"tray_info_idx\": \"GFA03\", 139 | \"tray_type\": \"PLA\", 140 | \"tray_sub_brands\": \"PLA Impact\" 141 | } 142 | 143 | var Bambu_PLA_Metal = { 144 | \"tray_info_idx\": \"GFA02\", 145 | \"tray_type\": \"PLA\", 146 | \"tray_sub_brands\": \"PLA Metal\" 147 | } 148 | 149 | var filament_library = { 150 | \"GFU99\": Generic_TPU, 151 | \"GFS99\": Generic_PVA, 152 | \"GFL98\": Generic_PLACF, 153 | \"GFL99\": Generic_PLA, 154 | \"GFG99\": Generic_PETG, 155 | \"GFC99\": Generic_PC, 156 | \"GFN98\": Generic_PACF, 157 | \"GFN99\": Generic_PA, 158 | \"GFB98\": Generic_ASA, 159 | \"GFB99\": Generic_ABS, 160 | \"GFU01\": Bambu_TPU_95A, 161 | \"GFS00\": Support_W, 162 | \"GFS01\": Support_G, 163 | \"GFA01\": Bambu_PLA_Matte, 164 | \"GFA00\": Bambu_PLA_Basic, 165 | \"GFC00\": Bambu_PC, 166 | \"GFN03\": Bambu_PACF, 167 | \"GFB00\": Bambu_ABS, 168 | \"GFL01\": PolyTerra_PLA, 169 | \"GFL00\": PolyLite_PLA 170 | } 171 | 172 | if (msg.payload.print.ams !== undefined) { 173 | for (var ams of msg.payload.print.ams.ams) { 174 | for (var tray of ams.tray) { 175 | if (tray.tray_info_idx !== undefined && tray.tray_info_idx !== \"\") { 176 | if(filament_library.hasOwnProperty(tray.tray_info_idx)) { 177 | var match = filament_library[tray.tray_info_idx]; 178 | tray.tray_type = match.tray_type; 179 | tray.tray_sub_brands = match.tray_sub_brands; 180 | if (tray.tray_diameter == \"0.00\") { 181 | tray.tray_diameter = \"1.75\"; 182 | } 183 | } 184 | } 185 | } 186 | } 187 | } 188 | if(msg.payload.print.vt_tray != undefined) { 189 | if (msg.payload.print.vt_tray.tray_info_idx != undefined) { 190 | if (filament_library.hasOwnProperty(msg.payload.print.vt_tray.tray_info_idx)) { 191 | var match = filament_library[msg.payload.print.vt_tray.tray_info_idx]; 192 | msg.payload.print.vt_tray.tray_type = match.tray_type; 193 | msg.payload.print.vt_tray.tray_sub_brands = match.tray_sub_brands; 194 | if (msg.payload.print.vt_tray.tray_diameter == \"0.00\") { 195 | msg.payload.print.vt_tray.tray_diameter = \"1.75\"; 196 | } 197 | } 198 | } 199 | } 200 | 201 | node.send(msg);", 202 | ``` 203 | 204 | 205 | https://github.com/EmileSpecialProducts/Turtle-ESP-mouse-jiggler -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [ ] Polylite manufacture codes 2 | - [ ] Setup static page with improv-serial 3 | See dutch developer site for example 4 | ``` 5 | 6 | ``` 7 | 8 | - [x] OpenSpool listed on improv website https://www.improv-wifi.com 9 | - [x] Setup static tag validator page 10 | - [x] Fix error when writing tags, worked in 0.1.41, but broken in master 11 | - [x] Add BambuRFID creator button 12 | - [x] Add openspool-xxxx.local creator button 13 | - [x] Fix wifi blinking blue 14 | - [x] Enable OTA updates on D1 mini 15 | - [x] Add breathing animation 16 | - [x] Add rainbow breathing 17 | - [ ] Add magenta blinking 18 | - [ ] Increase all 512 json docs to 1024 in prep for OpenTag standard 19 | - [ ] MQTT reboots afeter x minutes if not connected 20 | - [x] Add esp32-s3 support to manifest.json 21 | 22 | # Hardware 23 | 24 | - [x] Add 3mm offset to led light pipe 25 | - [x] Print final version of mini 26 | - [ ] Upload mini to MakerWorld so I start earning points 27 | - [x] Make PCB v2 for mini 28 | - [ ] Make PCB for Pro/AMS 29 | - [ ] Test aluminum Tape 30 | - [ ] Test RFID antennas 31 | 32 | # Social 33 | 34 | - [ ] Share on tiktok 35 | - [ ] Share RFID Tag video 36 | - [ ] Send 10 units to youtubers 37 | - [ ] Host on etsy shop 38 | - [ ] Host on tindie.com 39 | 40 | # Pipeline 41 | - [ ] Create releases with .bin files so people can install old versions 42 | 43 | # Bug 44 | - [x] Fix app crashing when writing tags 45 | - 0.1.36 ✅ (with and without mqtt) 46 | note: it did take a couple of tries and the tag needed to be on the reader when pushing the write button 47 | - 0.1.42 ❌ broken because I started trying to pass object into script 48 | - Upgrade esp-web-tool to latest once this is fixed: https://github.com/esphome/esp-web-tools/issues/515#issuecomment-2525463852 49 | 50 | # Features 51 | 52 | - [ ] Add nominal diameter to protocol 53 | - [ ] Add 'weight' data to nfc tag 54 | - [ ] Read bambu tags 55 | - [ ] Read creality tags 56 | - [ ] Support simultanious RFID tags on pn532 57 | 58 | # Misc 59 | - [ ] Enable status LED on s3 boards when mqtt connected 60 | - [ ] Bambu support 61 | - [ ] Octoprint support 62 | - [ ] Klipper support -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .jekyll-cache 3 | .sass-cache 4 | .jekyll-metadata 5 | Gemfile.lock -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem "jekyll", "~> 4.3.4" # installed by `gem jekyll` 4 | # gem "webrick" # required when using Ruby >= 3 and Jekyll <= 4.2.2 5 | 6 | gem "just-the-docs", "0.10.0" # pinned to the current release 7 | # gem "just-the-docs" # always download the latest release 8 | gem "jekyll-default-layout" 9 | 10 | gem "kramdown" 11 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Run bundle exec jekyll serve 2 | 3 | # defualt to serve 4 | all: clean serve 5 | 6 | .PHONY: clean 7 | clean: 8 | bundle exec jekyll clean 9 | 10 | .PHONY: serve 11 | serve: 12 | bundle exec jekyll serve --incremental --livereload --host 0.0.0.0 --port 4000 13 | 14 | .PHONY: build 15 | build: 16 | bundle exec jekyll build 17 | 18 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | ```bash 4 | make 5 | ``` 6 | 7 | or 8 | 9 | ```bash 10 | make serve 11 | ``` 12 | 13 | or 14 | 15 | ```bash 16 | make build 17 | ``` 18 | 19 | The page will then be served at [http://localhost:4000](http://localhost:4000) -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | title: OpenSpool 3 | description: Your filament wants to be free 4 | theme: just-the-docs 5 | url: https://openspool.io 6 | aux_links: 7 | "Edit this page on Github": 8 | - https://github.com/spuder/openspool 9 | logo: "images/OpenSpoolLogoMedium1.png" 10 | favicon_ico: "favicon.ico" 11 | color_scheme: dark 12 | plugins: 13 | - jekyll-default-layout 14 | head_scripts: 15 | # https://github.com/esphome/esp-web-tools/issues/515#issuecomment-2387664391 16 | # - https://unpkg.com/@improv-wifi/sdk-serial@1.1.0/dist/serial.min.js?module 17 | - https://unpkg.com/esp-web-tools@8.0.6/dist/web/install-button.js?module 18 | # - https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js 19 | defaults: 20 | - scope: 21 | path: "assets/js" 22 | values: 23 | js: true 24 | callouts: 25 | warning: 26 | title: Warning 27 | color: red 28 | note: 29 | title: Note 30 | color: blue 31 | important: 32 | title: Important 33 | color: blue 34 | caution: 35 | title: Caution 36 | color: yellow 37 | 38 | # footer_content: "Copyright © 2017-2020 Patrick Marsceill. Distributed by an MIT license." 39 | 40 | # # Footer "Edit this page on GitHub" link text 41 | # gh_edit_link: true # show or hide edit this page link 42 | # gh_edit_link_text: "Edit this page on GitHub." 43 | # gh_edit_repository: "https://github.com/just-the-docs/just-the-docs" # the github URL for your repo 44 | # gh_edit_branch: "main" # the branch that your docs is served from 45 | # # gh_edit_source: docs # the source that your files originate from 46 | # gh_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump into the editor immediately 47 | -------------------------------------------------------------------------------- /docs/_includes/footer_custom.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /docs/_includes/head_custom.html: -------------------------------------------------------------------------------- 1 | {% if page.custom_js %} 2 | 3 | {% endif %} 4 | -------------------------------------------------------------------------------- /docs/_plugins/file_size.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module FileSize 3 | def file_size(input) 4 | File.size(input) 5 | end 6 | end 7 | end 8 | 9 | Liquid::Template.register_filter(Jekyll::FileSize) -------------------------------------------------------------------------------- /docs/_sass/custom/custom.scss: -------------------------------------------------------------------------------- 1 | esp-web-install-button { 2 | display: block; 3 | margin: 20px 0; 4 | } 5 | 6 | .code-example { 7 | text-align: center; 8 | padding: 2rem; 9 | } 10 | 11 | .site-footer { 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | width: 100%; 16 | } 17 | 18 | .footer-links ul, 19 | .footer-links ul li { 20 | list-style-type: none !important; 21 | list-style-image: none !important; 22 | padding-left: 0 !important; 23 | } 24 | 25 | .footer-links li::before { 26 | content: none !important; 27 | display: none !important; 28 | } 29 | 30 | .footer-links li { 31 | margin-right: 20px; 32 | display: inline-block; 33 | } 34 | 35 | .footer-badge img { 36 | max-width: 100px; 37 | height: auto; 38 | } -------------------------------------------------------------------------------- /docs/assets/js/tag-handler.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | const params = new URLSearchParams(window.location.search); 3 | const noParamsMessage = document.getElementById('noParamsMessage'); 4 | let spoolData = {}; 5 | let colorParam; 6 | 7 | if (params.toString() === '') { 8 | // No parameters case 9 | noParamsMessage.style.display = 'block'; 10 | } else { 11 | // Parameters exist 12 | noParamsMessage.style.display = 'none'; 13 | colorParam = params.get('color_hex'); 14 | 15 | // Set spoolData 16 | spoolData = { 17 | protocol: params.get('protocol'), 18 | version: params.get('version'), 19 | type: params.get('type'), 20 | color_hex: colorParam, 21 | brand: params.get('brand'), 22 | min_temp: params.get('min_temp'), 23 | max_temp: params.get('max_temp') 24 | }; 25 | 26 | // Handle the color display 27 | const colorBox = document.getElementById('colorBox'); 28 | const hexValue = document.getElementById('hexValue'); 29 | if (colorBox && colorParam) { 30 | let color = colorParam; 31 | if (/^[0-9A-Fa-f]{6}$/.test(color)) { 32 | color = '#' + color; 33 | } 34 | colorBox.style.backgroundColor = color; 35 | hexValue.textContent = color.toUpperCase(); 36 | } else { 37 | // Set default or placeholder values 38 | colorBox.style.backgroundColor = '#CCCCCC'; 39 | hexValue.textContent = '?'; 40 | } 41 | 42 | // Update the individual fields 43 | const updateElement = (id, value, suffix = '') => { 44 | const element = document.getElementById(id); 45 | if (element) { 46 | element.textContent = value ? value + suffix : '?'; 47 | } 48 | }; 49 | 50 | // Update all fields 51 | updateElement('protocol', spoolData.protocol); 52 | updateElement('version', spoolData.version); 53 | updateElement('type', spoolData.type); 54 | updateElement('brand', spoolData.brand); 55 | 56 | // Special handling for temperature range 57 | const tempRange = document.getElementById('temp-range'); 58 | if (tempRange) { 59 | const minTemp = spoolData.min_temp ? `${spoolData.min_temp}°C` : '?'; 60 | const maxTemp = spoolData.max_temp ? `${spoolData.max_temp}°C` : '?'; 61 | tempRange.textContent = `${minTemp} - ${maxTemp}`; 62 | } 63 | 64 | // Show all fields, including temperature 65 | const dataItems = document.querySelectorAll('.data-item'); 66 | dataItems.forEach(item => { 67 | item.style.display = 'flex'; 68 | }); 69 | 70 | // Show raw json data 71 | const jsonDisplay = document.getElementById('jsonDisplay'); 72 | if (jsonDisplay) { 73 | const prettyJson = JSON.stringify(spoolData, null, 2) 74 | .replace(/null/g, '"?"'); 75 | jsonDisplay.textContent = prettyJson; 76 | } 77 | 78 | // Show url display 79 | const urlNdefDisplay = document.getElementById('urlNdefDisplay'); 80 | if (urlNdefDisplay) { 81 | // Create URL with parameters 82 | const url = new URL('https://openspool.io/tag_info'); 83 | 84 | // Only add parameters that exist and have values 85 | Object.entries(spoolData).forEach(([key, value]) => { 86 | if (value) { 87 | url.searchParams.set(key, value); 88 | } 89 | }); 90 | 91 | // Set the URL text content and make it visible 92 | urlNdefDisplay.textContent = url.toString(); 93 | urlNdefDisplay.style.display = 'block'; // Make sure it's visible 94 | 95 | // Optional: Add a class for styling 96 | urlNdefDisplay.classList.add('url-display'); 97 | } 98 | // Generate QR code 99 | // try { 100 | // const qrcodeContainer = document.getElementById('qrcode'); 101 | // if (qrcodeContainer) { 102 | // qrcodeContainer.innerHTML = ''; 103 | // new QRCode(qrcodeContainer, { 104 | // text: JSON.stringify(spoolData), 105 | // width: 256, 106 | // height: 256, 107 | // colorDark: "#64B5F6", 108 | // colorLight: "#ffffff", 109 | // correctLevel: QRCode.CorrectLevel.H 110 | // }); 111 | // } 112 | // } catch (error) { 113 | // console.error('Error generating QR code:', error); 114 | // } 115 | } 116 | }); -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/favicon.ico -------------------------------------------------------------------------------- /docs/firmware/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Firmware Directory 4 | nav_exclude: true 5 | search_exclude: true 6 | --- 7 | 8 | {% assign files = site.static_files | where_exp: "file", "file.path contains '/firmware/'" %} 9 | 10 | ## Available Files 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for file in files %} 19 | 20 | 21 | 22 | 31 | 32 | {% endfor %} 33 |
NameLast ModifiedSize
{{ file.name }}{{ file.modified_time | date: "%Y-%m-%d %H:%M" }} 23 | {% assign file_path = file.path | prepend: site.source %} 24 | {% if file_path | file_exists %} 25 | {% assign file_size = file_path | file_size | divided_by: 1024.0 %} 26 | {{ file_size | round: 2 }} KB 27 | {% else %} 28 | N/A 29 | {% endif %} 30 |
-------------------------------------------------------------------------------- /docs/firmware/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OpenSpool", 3 | "version": "1.20.0", 4 | "builds": [ 5 | { 6 | "chipFamily": "ESP32-S2", 7 | "ota": { 8 | "path": "openspool-esp32s2.ota.bin", 9 | "md5": "c655020654c1f8896785503b2ee867a5", 10 | "summary": "Merge pull request #58 from raihei/main%0A%0AFixed Sunlu filaments and created LED feedback for write tag finished", 11 | "release_url": "https://openspool.io/firmware" 12 | }, 13 | "parts": [ 14 | { 15 | "path": "openspool-esp32s2.factory.bin", 16 | "offset": 0 17 | } 18 | ] 19 | }, 20 | { 21 | "chipFamily": "ESP32-S3", 22 | "ota": { 23 | "path": "openspool-esp32s3.ota.bin", 24 | "md5": "5e39e32f026390e7095ca37ab5de19bd", 25 | "summary": "Merge pull request #58 from raihei/main%0A%0AFixed Sunlu filaments and created LED feedback for write tag finished", 26 | "release_url": "https://openspool.io/firmware" 27 | }, 28 | "parts": [ 29 | { 30 | "path": "openspool-esp32s3.factory.bin", 31 | "offset": 0 32 | } 33 | ] 34 | } 35 | ], 36 | "new_install_prompt_erase": true 37 | } 38 | -------------------------------------------------------------------------------- /docs/firmware/openspool-esp32s2.factory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/firmware/openspool-esp32s2.factory.bin -------------------------------------------------------------------------------- /docs/firmware/openspool-esp32s2.factory.bin.md5: -------------------------------------------------------------------------------- 1 | da98b83714098dae8d01d4c407659c99 2 | -------------------------------------------------------------------------------- /docs/firmware/openspool-esp32s2.ota.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/firmware/openspool-esp32s2.ota.bin -------------------------------------------------------------------------------- /docs/firmware/openspool-esp32s2.ota.bin.md5: -------------------------------------------------------------------------------- 1 | c655020654c1f8896785503b2ee867a5 2 | -------------------------------------------------------------------------------- /docs/firmware/openspool-esp32s3.factory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/firmware/openspool-esp32s3.factory.bin -------------------------------------------------------------------------------- /docs/firmware/openspool-esp32s3.factory.bin.md5: -------------------------------------------------------------------------------- 1 | fc68ec9ebd2e39d18106da3f181c61f1 2 | -------------------------------------------------------------------------------- /docs/firmware/openspool-esp32s3.ota.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/firmware/openspool-esp32s3.ota.bin -------------------------------------------------------------------------------- /docs/firmware/openspool-esp32s3.ota.bin.md5: -------------------------------------------------------------------------------- 1 | 5e39e32f026390e7095ca37ab5de19bd 2 | -------------------------------------------------------------------------------- /docs/hardware/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hardware 3 | has_children: true 4 | has_toc: true 5 | nav_order: 3 6 | --- 7 | 8 |
9 |
10 | 11 | OpenSpoolMini2 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | OpenSpoolAMS1 20 | 21 | 22 | 23 | 24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /docs/hardware/openspool-ams.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: OpenSpool-AMS 4 | parent: Hardware 5 | nav_order: 2 6 | --- 7 | 8 | Coming soon 9 | {: .label .label-yellow } -------------------------------------------------------------------------------- /docs/hardware/openspool-mini.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: OpenSpool-Mini 4 | parent: Hardware 5 | nav_order: 1 6 | --- 7 | 8 | # OpenSpool Mini 9 | 10 | 11 |

12 | 13 |

14 | 15 | ## Links 16 | 17 | | --- | --- | 18 | | 3MF/STL Files | Coming soon | 19 | | Fusion360 CAD Files | Coming soon 20 | 21 | 22 | ## Wiring Diagram 23 | 24 | ![](../images/OpenSpoolMiniWiringDiagram2.png) 25 | 26 | ![](https://www.wemos.cc/en/latest/_images/s2_mini_v1.0.0_4_16x9.jpg) 27 | 28 | 29 | ## Assembly Video: 30 | 31 | Coming soon 32 | {: .label .label-yellow } 33 | -------------------------------------------------------------------------------- /docs/images/BambuLogoLarge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/BambuLogoLarge.png -------------------------------------------------------------------------------- /docs/images/BambuLogoSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/BambuLogoSmall.png -------------------------------------------------------------------------------- /docs/images/IMG_5066.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/IMG_5066.heic -------------------------------------------------------------------------------- /docs/images/NFC1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/NFC1.png -------------------------------------------------------------------------------- /docs/images/NFC3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/NFC3.png -------------------------------------------------------------------------------- /docs/images/OpenSpoolAMS1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/OpenSpoolAMS1.png -------------------------------------------------------------------------------- /docs/images/OpenSpoolLogoMedium1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/OpenSpoolLogoMedium1.png -------------------------------------------------------------------------------- /docs/images/OpenSpoolMini1.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/OpenSpoolMini1.heic -------------------------------------------------------------------------------- /docs/images/OpenSpoolMini2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/OpenSpoolMini2.png -------------------------------------------------------------------------------- /docs/images/OpenSpoolMini3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/OpenSpoolMini3.png -------------------------------------------------------------------------------- /docs/images/OpenSpoolMiniWiringDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/OpenSpoolMiniWiringDiagram.png -------------------------------------------------------------------------------- /docs/images/OpenSpoolMiniWiringDiagram2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/OpenSpoolMiniWiringDiagram2.png -------------------------------------------------------------------------------- /docs/images/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/images/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/favicon-16x16.png -------------------------------------------------------------------------------- /docs/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/favicon-32x32.png -------------------------------------------------------------------------------- /docs/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/favicon.ico -------------------------------------------------------------------------------- /docs/images/phoneNFC.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/phoneNFC.jpeg -------------------------------------------------------------------------------- /docs/images/printersettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/printersettings.png -------------------------------------------------------------------------------- /docs/images/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /docs/images/wifi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/docs/images/wifi1.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | layout: home 4 | nav_order: 1 5 | --- 6 | 7 | # 📟 OpenSpool Firmware Installation 8 | 9 | ![](https://img.shields.io/badge/openspool-1.20.0-magenta) 10 | 11 |
12 | 13 | 14 |

🔩 Install the firmware

15 | 16 |
17 | 19 | Only Google Chrome / Edge Supported 20 | 21 | 🔒 HTTPS Required 🔒 22 | 23 |
24 | 25 |
26 |

Installation Instructions

27 |

1. Connect your OpenSpool device via USB

28 |

2. Press & Hold D0 Button

29 |

3. Press RST Button

30 |

4. Release both D0 and RST simultaneously

31 |

5. Click the blue 'Connect' button above

32 |

6. Follow the installation wizard

33 |
34 |
35 | 36 | {: .caution } 37 | If the above step fails, see the [README on github](https://github.com/spuder/OpenSpool) to upload manually with command `make lolin_s2_mini` or `make lolin_s3_mini`. 38 | 39 | # 🛠️ Configuration 40 | 41 | Once the firmware is installed, You will need to join the RFID reader to the same network as your 3d printer. 42 | 43 | ## 🛜 Wireless Configuration 44 | 45 | 1. Join the wifi network `OpenSpool` 46 | 2. Navigate to [192.168.4.1](http://192.168.4.1) 47 | 3. Enter your wifi credentials (take note of the mac address, you will need it to connect) 48 | 4. Reboot the RFID Reader 49 | 50 | ![](./images/wifi1.png) 51 | 52 | After reboot connect to http://openspool-d89be4.local (where d89be is your specific mac address) 53 | 54 | 55 | ## 🖨️ Printer Configuration 56 | 57 | Once openspool has joined your network, navigate to `openspool-xxxxxx.local`, or ip address (where xxxxxx are the last 6 digits of the mac address). 58 | 59 | If you forgot the mac address, or if you computer is unable to resolve `.local` domains. You can always connect via ip address. To find the ip address, consult your router documentation, or search your arp table. Instructions below. 60 | 61 | {: .note-title } 62 | > Finding IP Address via ARP Tables 63 | > 64 | > Mac/Linux: `arp -a | grep openspool` 65 | > Windows: `arp -a` 66 | 67 | 68 | Enter your Printer IP, LAN Access Code and Serial Number 69 | 70 | ![](./images/printersettings.png) 71 | 72 | {: .note-title } 73 | > Security 74 | > 75 | > `Printer IP`, `Lan Access Code` and `Serial Number` are stored locally on the microcontroller and never leave the device. Do not share your Acces Code with anyone. 76 | 77 | -------------------------------------------------------------------------------- /docs/makerchip.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Maker Chip 3 | layout: default 4 | permalink: /makerchip 5 | nav_exclude: true 6 | --- 7 | 8 | ## 🎉 Congratulations! 🎊 9 | 10 | ### 🌟 You have acquired an exclusive OpenSpool Maker Chip! 🌟 11 | 12 |
13 |
Awesome You Are Awesome GIFfrom Awesome GIFs
14 |
15 | 16 | 17 | #### 🏆 Welcome to the OpenSpool Maker community! 🚀 -------------------------------------------------------------------------------- /docs/privacy_policy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Privacy Policy 3 | layout: default 4 | permalink: /privacy_policy 5 | --- 6 | 7 | # Privacy Policy 8 | 9 | **Effective Date:** Jan 14, 2025 10 | 11 | Thank you for using OpenSpool! Your privacy is important to us. This Privacy Policy explains how we handle your information when you use our software. 12 | 13 | ## Data Collection 14 | OpenSpool does not collect, store, or process any personal data or identifiable information from its users. The software operates entirely on your device and does not transmit data to external servers or third-party services. 15 | 16 | ## Data Usage 17 | Since we do not collect or store any data, there is no user data to use, analyze, or share. 18 | 19 | ## Third-Party Services 20 | If the software integrates with third-party services (e.g., APIs or plugins), any data handled by those services is subject to their respective privacy policies. We encourage you to review the privacy policies of any third-party services you choose to use with OpenSpool. 21 | 22 | ## Open Source Nature 23 | OpenSpool is open source, meaning its source code is publicly available and can be reviewed, modified, or contributed to by anyone. This transparency ensures that you can verify how the software operates and confirm that it does not collect or store data. 24 | 25 | ## User Responsibility 26 | As an open-source project, OpenSpool provides tools that run locally on your device. You are responsible for maintaining the security and privacy of your device and any data you choose to process with the software. 27 | 28 | ## Changes to This Privacy Policy 29 | We reserve the right to update this Privacy Policy to reflect changes in the software or legal requirements. Any updates will be clearly posted in the project's repository or official website. 30 | 31 | ## Contact Us 32 | If you have any questions or concerns about this Privacy Policy, feel free to contact us: 33 | - [https://github.com/spuder/openspool] 34 | -------------------------------------------------------------------------------- /docs/rfid.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: RFID Tags 4 | nav_order: 2 5 | --- 6 | 7 |

8 | 9 |

10 | 11 | # RFID Tags 12 | 13 | At the heart of OpenSpool are the `NFC/RFID` tags that you will place on every spool you want to automate. The OpenSpool hardware works with a wide variety of High-Frequency tags (13.56Mhz) 14 | 15 | {: .note-title } 16 | > NFC vs RFID 17 | > 18 | > `NFC` is to `RFID` like `Bluetooth` is to `Radio`. 19 | > All NFC Tags are RFID tags, but not all RFID tags are NFC tags. 20 | 21 | ### Tag Selection 22 | 23 | Purchase NTAG 215 or 216 24 | 25 | [NTAG 215 - Amazon](https://a.co/d/5ojDUNk) 26 | [NTAG 215 - Aliexpress](https://www.aliexpress.us/item/3256806144939092.html) 27 | 28 | | Tag | Bytes | Supported | 29 | | --- | --- | --- | 30 | | NTAG 213 | 144 bytes | ❌ | 31 | | NTAG 215 | 504 bytes | ✅ | 32 | | NTAG 216 | 888 bytes | ✅ | 33 | 34 | 35 | ## Protocol 36 | 37 | OpenSpool NFC tags create an NDEF message on the tag. NDEF messages are human readable and can be viewed from your phone. 38 | 39 | [iPhone - NFC Tools app](https://apps.apple.com/us/app/nfc-tools/id1252962749) 40 | [Android - NFC Tools app](https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://play.google.com/store/apps/details%3Fid%3Dcom.wakdev.wdnfc%26hl%3Den_US&ved=2ahUKEwicptyi7caJAxXomO4BHZo4FIgQFnoECAwQAQ&usg=AOvVaw0XjR90J9AV8I4375hycopuz) 41 | 42 | The NDEF message is a single record of type `application/json` 43 | 44 | ```json 45 | { 46 | "protocol": "openspool", 47 | "version": "1.0", 48 | "type": "PLA", 49 | "color_hex": "FFAABB", 50 | "brand": "Generic", 51 | "min_temp": "220", 52 | "max_temp": "240" 53 | } 54 | ``` 55 | 56 | `color_hex` = 6 digit hex color. For best results it needs to be one of [the predefined values](https://github.com/bambulab/BambuStudio/blob/733531b1c68e755da991c9503a09c2206c2e4984/src/slic3r/GUI/AMSMaterialsSetting.cpp#L1398-L1425) in bambu slicer. 57 | `type` = One of [the predfined values](https://github.com/bambulab/BambuStudio/blob/733531b1c68e755da991c9503a09c2206c2e4984/src/libslic3r/PrintConfig.cpp#L1577-L1609) in bambu slicer. 58 | `brand` = One of `Generic`, `Overture` `PolyLite`, `eSun`, `PolyTerra`. The full list can be found [in bambuslicer](https://github.com/bambulab/BambuStudio/tree/master/resources/profiles/BBL/filament). If a filament is defined that is not in this list, it will be assumed to be `Generic`. 59 | 60 | 61 | ![](./images/phoneNFC.jpeg) -------------------------------------------------------------------------------- /extras/firmware_upload/batch_upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FIRMWARE_PATH="../../docs/firmware/openspool-esp32s3.factory.bin" 4 | DEVICE_PREFIX="/dev/cu.usbmodem" 5 | 6 | RED='\033[0;31m' 7 | GREEN='\033[0;32m' 8 | NC='\033[0m' # No Color 9 | 10 | print_red() { 11 | echo -e "${RED}$1${NC}" 12 | } 13 | 14 | print_green() { 15 | echo -e "${GREEN}$1${NC}" 16 | } 17 | 18 | flash_device() { 19 | local device=$1 20 | echo "Flashing device: $device" 21 | esptool.py --chip esp32-s3 -p "$device" --baud 921600 --before default_reset --after hard_reset write_flash 0x0 "$FIRMWARE_PATH" 22 | if [ $? -eq 0 ]; then 23 | print_green "Flashing successful: $device" 24 | else 25 | print_red "Flashing failed: $device" 26 | fi 27 | } 28 | 29 | watch_for_devices() { 30 | local old_devices="" 31 | while true; do 32 | local new_devices=$(ls ${DEVICE_PREFIX}* 2>/dev/null) 33 | for device in $new_devices; do 34 | if [[ ! $old_devices =~ $device ]]; then 35 | echo "New device detected: $device" 36 | flash_device "$device" 37 | fi 38 | done 39 | old_devices="$new_devices" 40 | sleep 1 41 | done 42 | } 43 | 44 | echo "Starting ESP32-S3 flashing script" 45 | echo "Firmware path: $FIRMWARE_PATH" 46 | echo "Watching for devices with prefix: $DEVICE_PREFIX" 47 | watch_for_devices 48 | -------------------------------------------------------------------------------- /extras/shipping/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Usage 3 | 4 | `python3 -m venv` 5 | 6 | ## Harmonized Shipment Number 7 | 8 | `8542.31.00` 9 | -------------------------------------------------------------------------------- /extras/shipping/get_mac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | import re 5 | import sys 6 | import qrcode 7 | from PIL import Image, ImageDraw, ImageFont 8 | 9 | def get_esp32_mac(): 10 | try: 11 | result = subprocess.run(['esptool.py', 'read_mac'], capture_output=True, text=True, check=True) 12 | mac_addresses = re.findall(r'MAC: ([0-9A-Fa-f:]{17})', result.stdout) 13 | 14 | if len(mac_addresses) == 0: 15 | print("Error: No ESP32 device found.") 16 | sys.exit(1) 17 | elif len(mac_addresses) == 2 and mac_addresses[0] == mac_addresses[1]: 18 | return mac_addresses[0] # Return the single MAC address if both are identical 19 | elif len(mac_addresses) > 1: 20 | print("Error: Multiple ESP32 devices detected. Please connect only one device.") 21 | sys.exit(1) 22 | 23 | return mac_addresses[0] 24 | except subprocess.CalledProcessError: 25 | print("Error: Failed to read MAC address. Make sure esptool.py is installed and the device is connected.") 26 | sys.exit(1) 27 | 28 | def generate_qr_code_with_text(url): 29 | # Generate QR code 30 | qr = qrcode.QRCode(version=1, box_size=10, border=4) 31 | qr.add_data(url) 32 | qr.make(fit=True) 33 | qr_img = qr.make_image(fill_color="black", back_color="white") 34 | 35 | # Convert to RGB mode if it's not already 36 | if qr_img.mode != 'RGB': 37 | qr_img = qr_img.convert('RGB') 38 | 39 | # Set up font 40 | try: 41 | font = ImageFont.truetype("Arial.ttf", 20) 42 | except IOError: 43 | font = ImageFont.load_default() 44 | 45 | # Calculate text size 46 | draw = ImageDraw.Draw(qr_img) 47 | bbox = draw.textbbox((0, 0), url, font=font) 48 | text_width = bbox[2] - bbox[0] 49 | text_height = bbox[3] - bbox[1] 50 | 51 | # Create a new image with space for QR code and text 52 | padding = 20 # Padding around the QR code and text 53 | img_width = max(qr_img.width, text_width) + (padding * 2) 54 | img_height = qr_img.height + text_height + (padding * 3) # Extra padding between QR and text 55 | img = Image.new('RGB', (img_width, img_height), color='white') 56 | 57 | # Paste QR code 58 | qr_x = (img_width - qr_img.width) // 2 59 | qr_y = padding 60 | img.paste(qr_img, (qr_x, qr_y)) 61 | 62 | # Add text below QR code 63 | draw = ImageDraw.Draw(img) 64 | text_x = (img_width - text_width) // 2 65 | text_y = qr_y + qr_img.height + padding 66 | draw.text((text_x, text_y), url, font=font, fill="black") 67 | 68 | # Display the image 69 | img.show() 70 | 71 | # Optionally, save the image 72 | # img.save("qr_code_with_url.png") 73 | 74 | def main(): 75 | mac_address = get_esp32_mac() 76 | last_six_digits = mac_address.replace(':', '')[-6:] 77 | url = f"http://openspool-{last_six_digits.lower()}.local" 78 | print(f"Generated URL: {url}") 79 | 80 | generate_qr_code_with_text(url) 81 | print("QR code with URL text has been generated and displayed.") 82 | 83 | if __name__ == "__main__": 84 | main() 85 | -------------------------------------------------------------------------------- /extras/stickers/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by venv; see https://docs.python.org/3/library/venv.html 2 | bin/ 3 | include/ 4 | lib/ 5 | pyvenv.cfg 6 | -------------------------------------------------------------------------------- /extras/stickers/1-Inch-Template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/extras/stickers/1-Inch-Template.pdf -------------------------------------------------------------------------------- /extras/stickers/BambuLogo1Inch.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/extras/stickers/BambuLogo1Inch.pdf -------------------------------------------------------------------------------- /extras/stickers/BambuLogo1Inch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/extras/stickers/BambuLogo1Inch.png -------------------------------------------------------------------------------- /extras/stickers/OpenSpoolLogo1Inch.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/extras/stickers/OpenSpoolLogo1Inch.pdf -------------------------------------------------------------------------------- /extras/stickers/OpenSpoolLogo1Inch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/extras/stickers/OpenSpoolLogo1Inch.png -------------------------------------------------------------------------------- /extras/stickers/README.md: -------------------------------------------------------------------------------- 1 | # Stickers 2 | 3 | Buy thsese Stickers: [https://amzn.to/40FfteV](https://amzn.to/40FfteV) 4 | 5 | 6 | ## Generating templates 7 | 8 | You can modify the templates yourself with python 9 | 10 | ```bash 11 | python3 -m venv . 12 | source ./bin/activate 13 | python3 -m pip install reportlab 14 | ``` 15 | 16 | ```bash 17 | python generate-template.py 18 | python generate-stickers.py 19 | ``` -------------------------------------------------------------------------------- /extras/stickers/generate-stickers.py: -------------------------------------------------------------------------------- 1 | from reportlab.lib.pagesizes import letter 2 | from reportlab.pdfgen import canvas 3 | from reportlab.lib.units import inch 4 | from PIL import Image 5 | import io 6 | import os 7 | import tempfile 8 | import shutil 9 | 10 | # User modifiable parameters 11 | LEFT_MARGIN = 3/8 # in inches 12 | RIGHT_MARGIN = 3/8 13 | TOP_MARGIN = 0.5 14 | BOTTOM_MARGIN = 0.5 15 | CIRCLE_DIAMETER = 1.0 # diameter of each circle in inches 16 | NUM_COLUMNS = 7 17 | NUM_ROWS = 9 18 | LOGO_PATHS = ["OpenSpoolLogo1Inch.png", "BambuLogo1Inch.png"] 19 | DPI = 300 # Increased DPI for better print quality 20 | 21 | def get_output_filename(logo_path): 22 | """ 23 | Generate output filename based on the input logo filename. 24 | """ 25 | base_name = os.path.splitext(os.path.basename(logo_path))[0] 26 | return f"{base_name}.pdf" 27 | 28 | def prepare_logo(logo_path, desired_size_inches): 29 | """ 30 | Prepare the logo image for insertion into PDF with high resolution. 31 | 32 | Args: 33 | logo_path (str): Path to the logo PNG file 34 | desired_size_inches (float): Desired size in inches 35 | 36 | Returns: 37 | str: Path to the temporary logo file 38 | """ 39 | # Create a temporary file with the same extension as the original 40 | temp_dir = tempfile.gettempdir() 41 | temp_logo_path = os.path.join(temp_dir, 'temp_logo.png') 42 | 43 | # Make a copy of the original logo 44 | shutil.copy2(logo_path, temp_logo_path) 45 | 46 | # Open the image 47 | img = Image.open(temp_logo_path) 48 | 49 | # Convert desired inches to pixels using higher DPI 50 | desired_size_pixels = int(desired_size_inches * DPI) 51 | 52 | needs_modification = False 53 | 54 | # Get original image dimensions 55 | original_width, original_height = img.size 56 | print(f"Original image size: {original_width}x{original_height} pixels") 57 | 58 | # Resize image if necessary, maintaining aspect ratio 59 | if img.size != (desired_size_pixels, desired_size_pixels): 60 | print(f"Resizing to {desired_size_pixels}x{desired_size_pixels} pixels") 61 | # Use high-quality resampling filter 62 | img = img.resize((desired_size_pixels, desired_size_pixels), 63 | Image.Resampling.LANCZOS) 64 | needs_modification = True 65 | 66 | # Convert to RGB if necessary (PDF doesn't support RGBA) 67 | if img.mode == 'RGBA': 68 | # Create a white background 69 | background = Image.new('RGB', img.size, (255, 255, 255)) 70 | # Paste the image using alpha channel as mask 71 | background.paste(img, mask=img.split()[3]) 72 | img = background 73 | needs_modification = True 74 | elif img.mode != 'RGB': 75 | img = img.convert('RGB') 76 | needs_modification = True 77 | 78 | # Save modifications to the temporary file if needed 79 | if needs_modification: 80 | img.save(temp_logo_path, 81 | 'PNG', 82 | dpi=(DPI, DPI), # Set the DPI metadata 83 | quality=95) # High quality for PNG compression 84 | 85 | return temp_logo_path 86 | 87 | def create_circle_pdf(logo_path, 88 | left_margin=LEFT_MARGIN*inch, 89 | right_margin=RIGHT_MARGIN*inch, 90 | top_margin=TOP_MARGIN*inch, 91 | bottom_margin=BOTTOM_MARGIN*inch, 92 | circle_diameter=CIRCLE_DIAMETER*inch, 93 | num_columns=NUM_COLUMNS, 94 | num_rows=NUM_ROWS, 95 | oversize_factor=1.06): 96 | """ 97 | Generate a PDF with evenly spaced circles and high-resolution logo overlays. 98 | """ 99 | output_filename = get_output_filename(logo_path) 100 | try: 101 | circle_diameter *= oversize_factor 102 | 103 | # Prepare the logo 104 | temp_logo_path = prepare_logo(logo_path, CIRCLE_DIAMETER) 105 | 106 | # Set up the canvas with high quality settings 107 | c = canvas.Canvas(output_filename, pagesize=letter) 108 | # Set PDF metadata 109 | c.setTitle(f"Sticker Sheet - {os.path.basename(logo_path)}") 110 | c.setAuthor("Sticker Generator") 111 | 112 | width, height = letter 113 | circle_radius = circle_diameter / 2 114 | num_circles = num_columns * num_rows 115 | 116 | # Calculate available space 117 | available_width = width - left_margin - right_margin 118 | available_height = height - top_margin - bottom_margin 119 | 120 | # Calculate spacing between circles 121 | total_circle_width = num_columns * circle_diameter 122 | total_gaps_width = available_width - total_circle_width 123 | x_spacing = total_gaps_width / (num_columns - 1) 124 | 125 | total_circle_height = num_rows * circle_diameter 126 | total_gaps_height = available_height - total_circle_height 127 | y_spacing = total_gaps_height / (num_rows - 1) 128 | 129 | # Draw circles and logos 130 | for i in range(num_rows): 131 | for j in range(num_columns): 132 | if i * num_columns + j < num_circles: 133 | # Calculate center positions 134 | x = left_margin + (j * (circle_diameter + x_spacing)) 135 | y = height - (top_margin + (i * (circle_diameter + y_spacing)) + circle_diameter) 136 | 137 | # Draw circle with precise line width 138 | c.setLineWidth(0.5) # Set a thinner line for cleaner circles 139 | c.circle(x + circle_radius, y + circle_radius, circle_radius, stroke=1, fill=0) 140 | 141 | # Draw logo with high quality settings 142 | c.drawImage(temp_logo_path, 143 | x, y, 144 | width=circle_diameter, 145 | height=circle_diameter, 146 | mask='auto', 147 | preserveAspectRatio=True) 148 | 149 | # Save the PDF with high quality compression 150 | c.setPageCompression(1) # Enable PDF compression 151 | c.save() 152 | print(f"Generated {output_filename} at {DPI} DPI") 153 | 154 | finally: 155 | # Clean up the temporary file 156 | if 'temp_logo_path' in locals() and os.path.exists(temp_logo_path): 157 | os.remove(temp_logo_path) 158 | 159 | def process_all_logos(): 160 | """ 161 | Process all logos in LOGO_PATHS and generate corresponding PDFs. 162 | """ 163 | for logo_path in LOGO_PATHS: 164 | if os.path.exists(logo_path): 165 | print(f"\nProcessing {logo_path}:") 166 | create_circle_pdf(logo_path) 167 | else: 168 | print(f"Warning: Logo file not found: {logo_path}") 169 | 170 | if __name__ == "__main__": 171 | # Generate PDF files for all logos 172 | process_all_logos() -------------------------------------------------------------------------------- /extras/stickers/generate-template.py: -------------------------------------------------------------------------------- 1 | from reportlab.lib.pagesizes import letter 2 | from reportlab.pdfgen import canvas 3 | from reportlab.lib.units import inch 4 | 5 | # User modifiable parameters 6 | LEFT_MARGIN = 3/8 # in inches 7 | RIGHT_MARGIN = 3/8 #3/8 8 | TOP_MARGIN = 0.5 9 | BOTTOM_MARGIN = 0.5 #1/2 10 | CIRCLE_DIAMETER = 1.0 # diameter of each circle in inches 11 | NUM_COLUMNS = 7 12 | NUM_ROWS = 9 13 | OUTPUT_FILENAME = "1-Inch-Template.pdf" 14 | 15 | def create_circle_pdf(filename, 16 | left_margin=LEFT_MARGIN*inch, 17 | right_margin=RIGHT_MARGIN*inch, 18 | top_margin=TOP_MARGIN*inch, 19 | bottom_margin=BOTTOM_MARGIN*inch, 20 | circle_diameter=CIRCLE_DIAMETER*inch, 21 | num_columns=NUM_COLUMNS, 22 | num_rows=NUM_ROWS): 23 | """ 24 | Generate a PDF with evenly spaced circles arranged in a grid. 25 | 26 | Args: 27 | filename (str): Output PDF filename 28 | left_margin (float): Left margin in points 29 | right_margin (float): Right margin in points 30 | top_margin (float): Top margin in points 31 | bottom_margin (float): Bottom margin in points 32 | circle_diameter (float): Diameter of each circle in points 33 | num_columns (int): Number of columns in the grid 34 | num_rows (int): Number of rows in the grid 35 | """ 36 | # Set up the canvas 37 | c = canvas.Canvas(filename, pagesize=letter) 38 | width, height = letter 39 | circle_radius = circle_diameter / 2 40 | num_circles = num_columns * num_rows 41 | 42 | # Calculate available space 43 | available_width = width - left_margin - right_margin 44 | available_height = height - top_margin - bottom_margin 45 | 46 | # Calculate spacing between circles 47 | total_circle_width = num_columns * circle_diameter 48 | total_gaps_width = available_width - total_circle_width 49 | x_spacing = total_gaps_width / (num_columns - 1) 50 | 51 | total_circle_height = num_rows * circle_diameter 52 | total_gaps_height = available_height - total_circle_height 53 | y_spacing = total_gaps_height / (num_rows - 1) 54 | 55 | # Draw circles 56 | for i in range(num_rows): 57 | for j in range(num_columns): 58 | if i * num_columns + j < num_circles: 59 | # Calculate center positions 60 | x = left_margin + (j * (circle_diameter + x_spacing)) + circle_radius 61 | y = height - (top_margin + (i * (circle_diameter + y_spacing)) + circle_radius) 62 | c.circle(x, y, circle_radius, stroke=1, fill=0) 63 | 64 | # Save the PDF 65 | c.save() 66 | 67 | if __name__ == "__main__": 68 | # Generate the PDF file with default parameters 69 | create_circle_pdf(OUTPUT_FILENAME) 70 | 71 | # Example of generating with custom parameters: 72 | # create_circle_pdf( 73 | # "custom-circles.pdf", 74 | # left_margin=0.75*inch, 75 | # right_margin=0.75*inch, 76 | # top_margin=1*inch, 77 | # bottom_margin=1*inch, 78 | # circle_diameter=0.8*inch, 79 | # num_columns=6, 80 | # num_rows=8 81 | # ) -------------------------------------------------------------------------------- /firmware/.gitignore: -------------------------------------------------------------------------------- 1 | /.esphome/ 2 | /secrets.yaml 3 | esphome_env/* 4 | 5 | *.pyc -------------------------------------------------------------------------------- /firmware/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # ls /dev/cu* 3 | # export USB_ADDRESS=/dev/cu.usbmodem14301 4 | USB_ADDRESS ?= $(error USB_ADDRESS is not set) 5 | 6 | .PHONY: all clean build upload dashboard logs 7 | 8 | all: build upload dashboard logs 9 | 10 | # Preserve backwards compatabliity in documentation 11 | # run-usb: run-usb-breadboard-mini 12 | # run-usb-pcb: run-usb-pcb-mini 13 | # build-pcb-mini: build-esp32s2-mini 14 | # build-pcb-ams: build-esp32s2-ams 15 | 16 | clean: 17 | esphome clean lolin_s2_mini.yaml 18 | esphome clean lolin_s3_mini.yaml 19 | esphome clean esp32-s3-devkitc-1.yaml 20 | rm -rf .esphome/idedata/* 21 | rm -rf ~/.platformio/penv 22 | # build-esp32s2-mini: 23 | # esphome config openspool-mini.yaml 24 | # esphome compile openspool-mini.yaml 25 | # build-esp32s2-ams: 26 | # esphome config openspool-ams.yaml 27 | # esphome compile openspool-ams.yaml 28 | # build-d1-mini-s2-breadboard: 29 | # esphome \ 30 | # -s version dev \ 31 | # -s spi2_miso_pin "GPIO39" \ 32 | # -s spi2_clk_pin "GPIO36" \ 33 | # -s spi2_mosi_pin "GPIO35" \ 34 | # -s rfid0_ss_pin "GPIO33" \ 35 | # -s spi2_type "any" \ 36 | # config openspool-mini.yaml 37 | # esphome \ 38 | # -s version dev \ 39 | # -s spi2_miso_pin "GPIO39" \ 40 | # -s spi2_clk_pin "GPIO36" \ 41 | # -s spi2_mosi_pin "GPIO35" \ 42 | # -s rfid0_ss_pin "GPIO33" \ 43 | # -s spi2_type "any" \ 44 | # compile openspool-mini.yaml 45 | # upload-mini: 46 | # @echo "Uploading to $(USB_ADDRESS)" 47 | # esphome upload openspool-mini.yaml --device $(USB_ADDRESS) 48 | # upload-ams: 49 | # @echo "Uploading to $(USB_ADDRESS)" 50 | # esphome upload openspool-ams.yaml --device $(USB_ADDRESS) 51 | # logs-mini: 52 | # @echo "Uploading to $(USB_ADDRESS)" 53 | # esphome logs openspool-mini.yaml --device $(USB_ADDRESS) 54 | dashboard: 55 | esphome dashboard . --open-ui --address 127.0.0.1 --port 6053 56 | # run-usb-breadboard-mini: 57 | # esphome \ 58 | # -s spi2_miso_pin "GPIO39" \ 59 | # -s spi2_clk_pin "GPIO36" \ 60 | # -s spi2_mosi_pin "GPIO35" \ 61 | # -s rfid0_ss_pin "GPIO33" \ 62 | # -s spi2_type "any" \ 63 | # run openspool-mini.yaml --device $(USB_ADDRESS) 64 | esp32-s3-devkitc-1: 65 | esphome run esp32-s3-devkitc-1.yaml --device $(USB_ADDRESS) 66 | lolin_s2_mini: 67 | esphome \ 68 | run lolin_s2_mini.yaml --device $(USB_ADDRESS) 69 | lolin_s3_mini: 70 | esphome \ 71 | run lolin_s3_mini.yaml --device $(USB_ADDRESS) 72 | devkit: 73 | esphome \ 74 | run esp32-s3-devkitc-1.yaml --device $(USB_ADDRESS) -------------------------------------------------------------------------------- /firmware/bambu.h: -------------------------------------------------------------------------------- 1 | // filament_mappings.h 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace bambulabs 9 | { 10 | const std::unordered_map filament_mappings = { 11 | {"TPU for AMS", "GFU02"}, 12 | {"TPU High Speed", "GFU00"}, 13 | {"TPU", "GFU99"}, 14 | {"PLA", "GFL99"}, 15 | {"PLA High Speed", "GFL95"}, 16 | {"PLA Silk", "GFL96"}, 17 | {"PETG", "GFG99"}, 18 | {"PET-CF", "GFG99"}, 19 | {"ASA", "GFB98"}, 20 | {"ABS", "GFB99"}, 21 | {"PC", "GFC99"}, 22 | {"PA", "GFN99"}, 23 | {"PA-CF", "GFN98"}, 24 | {"PLA-CF", "GFL98"}, 25 | {"PVA", "GFS99"}, 26 | {"BVOH", "GFS97"}, 27 | {"EVA", "GFR99"}, 28 | {"HIPS", "GFS98"}, 29 | {"PC", "GFC99"}, 30 | {"PCTG", "GFG97"}, 31 | {"PE", "GFP99"}, 32 | {"PE-CF", "GFP98"}, 33 | {"PHA", "GFR98"}, 34 | {"PP", "GFP97"}, 35 | {"PP-CF", "GFP96"}, 36 | {"PP-GF", "GFP95"}, 37 | {"PPA-CF", "GFN97"}, 38 | {"PPA-GF", "GFN96"}, 39 | {"Support", "GFS00"}}; 40 | 41 | // Special cases for brand-specific codes 42 | const std::unordered_map> brand_specific_codes = { 43 | {"PLA", {{"Bambu", "GFA00"}, {"PolyTerra", "GFL01"}, {"PolyLite", "GFL00"}, {"Sunlu", "GFSNL03"}}}, 44 | {"PLA Aero", {{"Bambu", "GFG01"}}}, 45 | {"TPU", {{"Bambu", "GFU01"}}}, 46 | {"ABS", {{"Bambu", "GFB00"}, {"PolyLite", "GFB60"}}}, 47 | {"ASA", {{"Bambu", "GFB01"}, {"PolyLite", "GFB61"}}}, 48 | {"PC", {{"Bambu", "GFC00"}}}, 49 | {"PA-CF", {{"Bambu", "GFN03"}}}, 50 | {"PET-CF", {{"Bambu", "GFT00"}}}, 51 | {"PETG HF", {{"Bambu", "GFG02"}}}, 52 | {"PETG Translucent", {{"Bambu", "GFG01"}}}, 53 | {"PETG", {{"Bambu", "GFG00"}, {"PolyLite", "GFG60"}, {"Sunlu", "GFSNL08"}}}}; 54 | 55 | // Function with two parameters 56 | inline std::string get_bambu_code(const std::string &type, const std::string &brand = "") 57 | { 58 | if (!brand.empty()) 59 | { 60 | auto brand_it = brand_specific_codes.find(type); 61 | if (brand_it != brand_specific_codes.end()) 62 | { 63 | auto code_it = brand_it->second.find(brand); 64 | if (code_it != brand_it->second.end()) 65 | { 66 | return code_it->second; 67 | } 68 | } 69 | } 70 | 71 | auto it = filament_mappings.find(type); 72 | if (it != filament_mappings.end()) 73 | { 74 | return it->second; 75 | } 76 | return ""; // Unknown type 77 | } 78 | 79 | // Function with three parameters (for Bambu PLA subtypes) 80 | inline std::string get_bambu_code(const std::string &type, const std::string &brand, const std::string &subtype) 81 | { 82 | if (type == "PLA" && brand == "Bambu") 83 | { 84 | if (subtype == "Matte") 85 | return "GFA01"; 86 | if (subtype == "Metal") 87 | return "GFA02"; 88 | if (subtype == "Impact") 89 | return "GFA03"; 90 | return "GFA00"; // Default to Basic for unknown subtypes 91 | } 92 | return get_bambu_code(type, brand); 93 | } 94 | 95 | // uint8_t ams_id, uint8_t ams_tray 96 | inline std::string generate_mqtt_payload(std::string openspool_tag_json, uint16_t ams_id, uint16_t ams_tray) 97 | { 98 | StaticJsonDocument<1024> doc_in; 99 | StaticJsonDocument<512> doc_out; 100 | 101 | DeserializationError error = deserializeJson(doc_in, openspool_tag_json); 102 | if (error) 103 | { 104 | ESP_LOGE("bambu", "Failed to parse input JSON: %s", error.c_str()); 105 | return {}; // skip publishing 106 | } 107 | 108 | // Check if 'version' key exists and its value is 1.0 109 | if (!doc_in.containsKey("version") || doc_in["version"].as() != "1.0") 110 | { 111 | ESP_LOGE("bambu", "Invalid or missing version. Expected version '1.0'"); 112 | return {}; // skip publishing 113 | } 114 | 115 | // Check if required fields are present 116 | const char *required_fields[] = {"color_hex", "min_temp", "max_temp", "brand", "type"}; 117 | for (const char *field : required_fields) 118 | { 119 | if (!doc_in.containsKey(field)) 120 | { 121 | ESP_LOGE("bambu", "Missing required field: %s", field); 122 | return {}; // skip publishing 123 | } 124 | } 125 | 126 | if (doc_in["color_hex"].as().length() != 6 && doc_in["color_hex"].as().length() != 8) { 127 | ESP_LOGE("bambu", "Invalid color_hex length (expected 6 or 8 characters)"); 128 | return {}; 129 | } 130 | 131 | JsonObject print = doc_out.createNestedObject("print"); 132 | print["sequence_id"] = "0"; 133 | print["command"] = "ams_filament_setting"; 134 | print["ams_id"] = ams_id; 135 | print["tray_id"] = ams_tray; 136 | if (doc_in["color_hex"].as().length() == 6) { 137 | print["tray_color"] = doc_in["color_hex"].as() + "FF"; 138 | } 139 | else{ 140 | print["tray_color"] = doc_in["color_hex"].as(); 141 | } 142 | print["nozzle_temp_min"] = uint16_t(doc_in["min_temp"]); // if not string or int, will fall back to 0 143 | print["nozzle_temp_max"] = uint16_t(doc_in["max_temp"]); // if not string or int, will fall back to 0 144 | print["tray_type"] = doc_in["type"]; 145 | print["setting_id"] = ""; 146 | print["tray_info_idx"] = get_bambu_code(doc_in["type"], doc_in["brand"]); 147 | // print["tray_sub_brands"] = doc_in["sub_brand"]; //TODO: support sub brands if needed 148 | 149 | std::string result; 150 | serializeJson(doc_out, result); 151 | 152 | if (result.empty()) 153 | { 154 | ESP_LOGE("bambu", "Failed to build JSON"); 155 | return {}; 156 | } 157 | 158 | ESP_LOGI("mqtt", "Publishing %s", result.c_str()); 159 | return result; 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /firmware/common.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | substitutions: 3 | name: openspool 4 | esphome: 5 | name: ${name} 6 | name_add_mac_suffix: true 7 | project: 8 | name: spuder.openspool 9 | version: ${version} 10 | min_version: 2024.11.0 11 | platformio_options: 12 | build_unflags: -std=gnu++11 13 | build_flags: 14 | - -std=gnu++14 15 | - -DMBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\" 16 | on_boot: 17 | then: 18 | #TODO: breahting blue studders a little bit 19 | - light.turn_on: 20 | id: neopixel_light 21 | effect: Breathing Blue 22 | brightness: 100% 23 | - wait_until: 24 | condition: 25 | wifi.connected: 26 | - delay: 100ms 27 | - light.turn_on: 28 | id: neopixel_light 29 | effect: none 30 | - delay: 100ms 31 | - light.turn_on: 32 | id: neopixel_light 33 | effect: Rainbow 34 | - delay: 4s 35 | - light.turn_on: 36 | id: neopixel_light 37 | effect: none 38 | brightness: 50% 39 | # - delay: 100ms 40 | # - light.addressable_set: 41 | # id: neopixel_light 42 | # color_brightness: 50% 43 | # range_from: 0 44 | # range_to: ${led_count} 45 | # red: 50% 46 | # green: 50% 47 | # blue: 50% 48 | - if: 49 | condition: 50 | lambda: |- 51 | return !id(bambu_lan_access_code).state.empty() && 52 | !id(bambu_ip_address).state.empty() && 53 | !id(bambu_serial_number).state.empty(); 54 | then: 55 | - logger.log: 56 | level: info 57 | format: "Connecting to Bambu printer" 58 | - mqtt.enable: 59 | id: bambu_mqtt 60 | else: 61 | - logger.log: 62 | level: info 63 | format: "Missing Bambu Credentials, skipping mqtt connect" 64 | - mqtt.disable: 65 | id: bambu_mqtt 66 | # - script.execute: set_all_leds_white 67 | # - lambda: |- 68 | # //TODO: this appears to have broken and no longer blinks blue 69 | # if (!wifi::global_wifi_component->is_connected() && 70 | # wifi::global_wifi_component->wifi_soft_ap_ip().str() == "192.168.4.1") { 71 | # id(set_led_breathing_blue).execute(); 72 | # } 73 | # - lambda: |- 74 | # id(my_ota).set_auth_password("New password"); 75 | on_shutdown: 76 | then: 77 | - script.execute: set_led_off 78 | esp32: 79 | framework: 80 | type: esp-idf 81 | version: 5.3.1 82 | platform_version: 6.9.0 # https://github.com/platformio/platform-espressif32/releases/ 83 | sdkconfig_options: 84 | CONFIG_MBEDTLS_HKDF_C: y # Needed for bambu KDF 85 | CONFIG_MBEDTLS_MD_C: y # Needed for bambu KDF 86 | # version: recommended 87 | # sdkconfig_options: 88 | # MBEDTLS_CERTIFICATE_BUNDLE: y 89 | # MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL: y 90 | 91 | #TODO: uncomment when ready for bambu support 92 | # external_components: 93 | # - source: 94 | # type: local 95 | # path: components 96 | # components: [pn532, nfc] 97 | 98 | packages: 99 | version: !include conf.d/version.yaml 100 | uptime: !include conf.d/uptime.yaml 101 | wifi: !include conf.d/wifi.yaml 102 | web_server: !include conf.d/web_server.yaml 103 | debug: !include conf.d/debug.yaml 104 | # time: !include conf.d/time.yaml 105 | mqtt_bambu_lan: !include conf.d/mqtt_bambu_lan.yaml 106 | filament: !include conf.d/filament.yaml 107 | bambu_printer: !include conf.d/bambu_printer.yaml 108 | pn532_rfid-solo: !include conf.d/pn532_rfid-solo.yaml 109 | automation: !include conf.d/automation.yaml 110 | led-external: !include conf.d/led-external.yaml 111 | ota: !include conf.d/ota.yaml 112 | #update: !include conf.d/update.yaml 113 | api: !include conf.d/api.yaml 114 | logger: !include conf.d/logger.yaml 115 | -------------------------------------------------------------------------------- /firmware/components/nfc/__init__.py: -------------------------------------------------------------------------------- 1 | from esphome import automation 2 | import esphome.codegen as cg 3 | 4 | CODEOWNERS = ["@jesserockz", "@kbx81"] 5 | 6 | nfc_ns = cg.esphome_ns.namespace("nfc") 7 | 8 | Nfcc = nfc_ns.class_("Nfcc") 9 | NfcTag = nfc_ns.class_("NfcTag") 10 | NfcTagListener = nfc_ns.class_("NfcTagListener") 11 | NfcOnTagTrigger = nfc_ns.class_( 12 | "NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag) 13 | ) 14 | -------------------------------------------------------------------------------- /firmware/components/nfc/automation.cpp: -------------------------------------------------------------------------------- 1 | #include "automation.h" 2 | 3 | namespace esphome { 4 | namespace nfc { 5 | 6 | void NfcOnTagTrigger::process(const std::unique_ptr &tag) { this->trigger(format_uid(tag->get_uid()), *tag); } 7 | 8 | } // namespace nfc 9 | } // namespace esphome 10 | -------------------------------------------------------------------------------- /firmware/components/nfc/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "esphome/core/automation.h" 5 | 6 | #include "nfc.h" 7 | 8 | namespace esphome { 9 | namespace nfc { 10 | 11 | class NfcOnTagTrigger : public Trigger { 12 | public: 13 | void process(const std::unique_ptr &tag); 14 | }; 15 | 16 | } // namespace nfc 17 | } // namespace esphome 18 | -------------------------------------------------------------------------------- /firmware/components/nfc/binary_sensor/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | import esphome.config_validation as cv 3 | from esphome.components import binary_sensor 4 | from esphome.const import CONF_UID 5 | from esphome.core import HexInt 6 | from .. import nfc_ns, Nfcc, NfcTagListener 7 | 8 | DEPENDENCIES = ["nfc"] 9 | 10 | CONF_NDEF_CONTAINS = "ndef_contains" 11 | CONF_NFCC_ID = "nfcc_id" 12 | CONF_TAG_ID = "tag_id" 13 | 14 | NfcTagBinarySensor = nfc_ns.class_( 15 | "NfcTagBinarySensor", 16 | binary_sensor.BinarySensor, 17 | cg.Component, 18 | NfcTagListener, 19 | cg.Parented.template(Nfcc), 20 | ) 21 | 22 | 23 | def validate_uid(value): 24 | value = cv.string_strict(value) 25 | for x in value.split("-"): 26 | if len(x) != 2: 27 | raise cv.Invalid( 28 | "Each part (separated by '-') of the UID must be two characters " 29 | "long." 30 | ) 31 | try: 32 | x = int(x, 16) 33 | except ValueError as err: 34 | raise cv.Invalid( 35 | "Valid characters for parts of a UID are 0123456789ABCDEF." 36 | ) from err 37 | if x < 0 or x > 255: 38 | raise cv.Invalid( 39 | "Valid values for UID parts (separated by '-') are 00 to FF" 40 | ) 41 | return value 42 | 43 | 44 | CONFIG_SCHEMA = cv.All( 45 | binary_sensor.binary_sensor_schema(NfcTagBinarySensor) 46 | .extend( 47 | { 48 | cv.GenerateID(CONF_NFCC_ID): cv.use_id(Nfcc), 49 | cv.Optional(CONF_NDEF_CONTAINS): cv.string, 50 | cv.Optional(CONF_TAG_ID): cv.string, 51 | cv.Optional(CONF_UID): validate_uid, 52 | } 53 | ) 54 | .extend(cv.COMPONENT_SCHEMA), 55 | cv.has_exactly_one_key(CONF_NDEF_CONTAINS, CONF_TAG_ID, CONF_UID), 56 | ) 57 | 58 | 59 | async def to_code(config): 60 | var = await binary_sensor.new_binary_sensor(config) 61 | await cg.register_component(var, config) 62 | await cg.register_parented(var, config[CONF_NFCC_ID]) 63 | 64 | hub = await cg.get_variable(config[CONF_NFCC_ID]) 65 | cg.add(hub.register_listener(var)) 66 | if CONF_NDEF_CONTAINS in config: 67 | cg.add(var.set_ndef_match_string(config[CONF_NDEF_CONTAINS])) 68 | if CONF_TAG_ID in config: 69 | cg.add(var.set_tag_name(config[CONF_TAG_ID])) 70 | elif CONF_UID in config: 71 | addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")] 72 | cg.add(var.set_uid(addr)) 73 | -------------------------------------------------------------------------------- /firmware/components/nfc/binary_sensor/binary_sensor.cpp: -------------------------------------------------------------------------------- 1 | #include "binary_sensor.h" 2 | #include "../nfc_helpers.h" 3 | #include "esphome/core/log.h" 4 | 5 | namespace esphome { 6 | namespace nfc { 7 | 8 | static const char *const TAG = "nfc.binary_sensor"; 9 | 10 | void NfcTagBinarySensor::setup() { 11 | this->parent_->register_listener(this); 12 | this->publish_initial_state(false); 13 | } 14 | 15 | void NfcTagBinarySensor::dump_config() { 16 | std::string match_str = "name"; 17 | 18 | LOG_BINARY_SENSOR("", "NFC Tag Binary Sensor", this); 19 | if (!this->match_string_.empty()) { 20 | if (!this->match_tag_name_) { 21 | match_str = "contains"; 22 | } 23 | ESP_LOGCONFIG(TAG, " Tag %s: %s", match_str.c_str(), this->match_string_.c_str()); 24 | return; 25 | } 26 | if (!this->uid_.empty()) { 27 | ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes(this->uid_).c_str()); 28 | } 29 | } 30 | 31 | void NfcTagBinarySensor::set_ndef_match_string(const std::string &str) { 32 | this->match_string_ = str; 33 | this->match_tag_name_ = false; 34 | } 35 | 36 | void NfcTagBinarySensor::set_tag_name(const std::string &str) { 37 | this->match_string_ = str; 38 | this->match_tag_name_ = true; 39 | } 40 | 41 | void NfcTagBinarySensor::set_uid(const std::vector &uid) { this->uid_ = uid; } 42 | 43 | bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr &msg) { 44 | for (const auto &record : msg->get_records()) { 45 | if (record->get_payload().find(this->match_string_) != std::string::npos) { 46 | return true; 47 | } 48 | } 49 | return false; 50 | } 51 | 52 | bool NfcTagBinarySensor::tag_match_tag_name(const std::shared_ptr &msg) { 53 | for (const auto &record : msg->get_records()) { 54 | if (record->get_payload().find(HA_TAG_ID_PREFIX) != std::string::npos) { 55 | auto rec_substr = record->get_payload().substr(sizeof(HA_TAG_ID_PREFIX) - 1); 56 | if (rec_substr.find(this->match_string_) != std::string::npos) { 57 | return true; 58 | } 59 | } 60 | } 61 | return false; 62 | } 63 | 64 | bool NfcTagBinarySensor::tag_match_uid(const std::vector &data) { 65 | if (data.size() != this->uid_.size()) { 66 | return false; 67 | } 68 | 69 | for (size_t i = 0; i < data.size(); i++) { 70 | if (data[i] != this->uid_[i]) { 71 | return false; 72 | } 73 | } 74 | return true; 75 | } 76 | 77 | void NfcTagBinarySensor::tag_off(NfcTag &tag) { 78 | if (!this->match_string_.empty() && tag.has_ndef_message()) { 79 | if (this->match_tag_name_) { 80 | if (this->tag_match_tag_name(tag.get_ndef_message())) { 81 | this->publish_state(false); 82 | } 83 | } else { 84 | if (this->tag_match_ndef_string(tag.get_ndef_message())) { 85 | this->publish_state(false); 86 | } 87 | } 88 | return; 89 | } 90 | if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) { 91 | this->publish_state(false); 92 | } 93 | } 94 | 95 | void NfcTagBinarySensor::tag_on(NfcTag &tag) { 96 | if (!this->match_string_.empty() && tag.has_ndef_message()) { 97 | if (this->match_tag_name_) { 98 | if (this->tag_match_tag_name(tag.get_ndef_message())) { 99 | this->publish_state(true); 100 | } 101 | } else { 102 | if (this->tag_match_ndef_string(tag.get_ndef_message())) { 103 | this->publish_state(true); 104 | } 105 | } 106 | return; 107 | } 108 | if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) { 109 | this->publish_state(true); 110 | } 111 | } 112 | 113 | } // namespace nfc 114 | } // namespace esphome 115 | -------------------------------------------------------------------------------- /firmware/components/nfc/binary_sensor/binary_sensor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/binary_sensor/binary_sensor.h" 4 | #include "esphome/components/nfc/nfc.h" 5 | #include "esphome/components/nfc/nfc_tag.h" 6 | #include "esphome/core/component.h" 7 | #include "esphome/core/helpers.h" 8 | 9 | namespace esphome { 10 | namespace nfc { 11 | 12 | class NfcTagBinarySensor : public binary_sensor::BinarySensor, 13 | public Component, 14 | public NfcTagListener, 15 | public Parented { 16 | public: 17 | void setup() override; 18 | void dump_config() override; 19 | 20 | void set_ndef_match_string(const std::string &str); 21 | void set_tag_name(const std::string &str); 22 | void set_uid(const std::vector &uid); 23 | 24 | bool tag_match_ndef_string(const std::shared_ptr &msg); 25 | bool tag_match_tag_name(const std::shared_ptr &msg); 26 | bool tag_match_uid(const std::vector &data); 27 | 28 | void tag_off(NfcTag &tag) override; 29 | void tag_on(NfcTag &tag) override; 30 | 31 | protected: 32 | bool match_tag_name_{false}; 33 | std::string match_string_; 34 | std::vector uid_; 35 | }; 36 | 37 | } // namespace nfc 38 | } // namespace esphome 39 | -------------------------------------------------------------------------------- /firmware/components/nfc/nci_core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/helpers.h" 4 | 5 | #include 6 | 7 | namespace esphome { 8 | namespace nfc { 9 | 10 | // Header info 11 | static const uint8_t NCI_PKT_HEADER_SIZE = 3; // NCI packet (pkt) headers are always three bytes 12 | static const uint8_t NCI_PKT_MT_GID_OFFSET = 0; // NCI packet (pkt) MT and GID offsets 13 | static const uint8_t NCI_PKT_OID_OFFSET = 1; // NCI packet (pkt) OID offset 14 | static const uint8_t NCI_PKT_LENGTH_OFFSET = 2; // NCI packet (pkt) message length (size) offset 15 | static const uint8_t NCI_PKT_PAYLOAD_OFFSET = 3; // NCI packet (pkt) payload offset 16 | // Important masks 17 | static const uint8_t NCI_PKT_MT_MASK = 0xE0; // NCI packet (pkt) message type mask 18 | static const uint8_t NCI_PKT_PBF_MASK = 0x10; // packet boundary flag bit 19 | static const uint8_t NCI_PKT_GID_MASK = 0x0F; 20 | static const uint8_t NCI_PKT_OID_MASK = 0x3F; 21 | // Message types 22 | static const uint8_t NCI_PKT_MT_DATA = 0x00; // For sending commands to NFC endpoint (card/tag) 23 | static const uint8_t NCI_PKT_MT_CTRL_COMMAND = 0x20; // For sending commands to NFCC 24 | static const uint8_t NCI_PKT_MT_CTRL_RESPONSE = 0x40; // Response from NFCC to commands 25 | static const uint8_t NCI_PKT_MT_CTRL_NOTIFICATION = 0x60; // Notification from NFCC 26 | // GIDs 27 | static const uint8_t NCI_CORE_GID = 0x0; 28 | static const uint8_t RF_GID = 0x1; 29 | static const uint8_t NFCEE_GID = 0x1; 30 | static const uint8_t NCI_PROPRIETARY_GID = 0xF; 31 | // OIDs 32 | static const uint8_t NCI_CORE_RESET_OID = 0x00; 33 | static const uint8_t NCI_CORE_INIT_OID = 0x01; 34 | static const uint8_t NCI_CORE_SET_CONFIG_OID = 0x02; 35 | static const uint8_t NCI_CORE_GET_CONFIG_OID = 0x03; 36 | static const uint8_t NCI_CORE_CONN_CREATE_OID = 0x04; 37 | static const uint8_t NCI_CORE_CONN_CLOSE_OID = 0x05; 38 | static const uint8_t NCI_CORE_CONN_CREDITS_OID = 0x06; 39 | static const uint8_t NCI_CORE_GENERIC_ERROR_OID = 0x07; 40 | static const uint8_t NCI_CORE_INTERFACE_ERROR_OID = 0x08; 41 | 42 | static const uint8_t RF_DISCOVER_MAP_OID = 0x00; 43 | static const uint8_t RF_SET_LISTEN_MODE_ROUTING_OID = 0x01; 44 | static const uint8_t RF_GET_LISTEN_MODE_ROUTING_OID = 0x02; 45 | static const uint8_t RF_DISCOVER_OID = 0x03; 46 | static const uint8_t RF_DISCOVER_SELECT_OID = 0x04; 47 | static const uint8_t RF_INTF_ACTIVATED_OID = 0x05; 48 | static const uint8_t RF_DEACTIVATE_OID = 0x06; 49 | static const uint8_t RF_FIELD_INFO_OID = 0x07; 50 | static const uint8_t RF_T3T_POLLING_OID = 0x08; 51 | static const uint8_t RF_NFCEE_ACTION_OID = 0x09; 52 | static const uint8_t RF_NFCEE_DISCOVERY_REQ_OID = 0x0A; 53 | static const uint8_t RF_PARAMETER_UPDATE_OID = 0x0B; 54 | 55 | static const uint8_t NFCEE_DISCOVER_OID = 0x00; 56 | static const uint8_t NFCEE_MODE_SET_OID = 0x01; 57 | // Interfaces 58 | static const uint8_t INTF_NFCEE_DIRECT = 0x00; 59 | static const uint8_t INTF_FRAME = 0x01; 60 | static const uint8_t INTF_ISODEP = 0x02; 61 | static const uint8_t INTF_NFCDEP = 0x03; 62 | static const uint8_t INTF_TAGCMD = 0x80; // NXP proprietary 63 | // Bit rates 64 | static const uint8_t NFC_BIT_RATE_106 = 0x00; 65 | static const uint8_t NFC_BIT_RATE_212 = 0x01; 66 | static const uint8_t NFC_BIT_RATE_424 = 0x02; 67 | static const uint8_t NFC_BIT_RATE_848 = 0x03; 68 | static const uint8_t NFC_BIT_RATE_1695 = 0x04; 69 | static const uint8_t NFC_BIT_RATE_3390 = 0x05; 70 | static const uint8_t NFC_BIT_RATE_6780 = 0x06; 71 | // Protocols 72 | static const uint8_t PROT_UNDETERMINED = 0x00; 73 | static const uint8_t PROT_T1T = 0x01; 74 | static const uint8_t PROT_T2T = 0x02; 75 | static const uint8_t PROT_T3T = 0x03; 76 | static const uint8_t PROT_ISODEP = 0x04; 77 | static const uint8_t PROT_NFCDEP = 0x05; 78 | static const uint8_t PROT_T5T = 0x06; 79 | static const uint8_t PROT_MIFARE = 0x80; 80 | // RF Technologies 81 | static const uint8_t NFC_RF_TECH_A = 0x00; 82 | static const uint8_t NFC_RF_TECH_B = 0x01; 83 | static const uint8_t NFC_RF_TECH_F = 0x02; 84 | static const uint8_t NFC_RF_TECH_15693 = 0x03; 85 | // RF Technology & Modes 86 | static const uint8_t MODE_MASK = 0xF0; 87 | static const uint8_t MODE_LISTEN_MASK = 0x80; 88 | static const uint8_t MODE_POLL = 0x00; 89 | 90 | static const uint8_t TECH_PASSIVE_NFCA = 0x00; 91 | static const uint8_t TECH_PASSIVE_NFCB = 0x01; 92 | static const uint8_t TECH_PASSIVE_NFCF = 0x02; 93 | static const uint8_t TECH_ACTIVE_NFCA = 0x03; 94 | static const uint8_t TECH_ACTIVE_NFCF = 0x05; 95 | static const uint8_t TECH_PASSIVE_15693 = 0x06; 96 | // Status codes 97 | static const uint8_t STATUS_OK = 0x00; 98 | static const uint8_t STATUS_REJECTED = 0x01; 99 | static const uint8_t STATUS_RF_FRAME_CORRUPTED = 0x02; 100 | static const uint8_t STATUS_FAILED = 0x03; 101 | static const uint8_t STATUS_NOT_INITIALIZED = 0x04; 102 | static const uint8_t STATUS_SYNTAX_ERROR = 0x05; 103 | static const uint8_t STATUS_SEMANTIC_ERROR = 0x06; 104 | static const uint8_t STATUS_INVALID_PARAM = 0x09; 105 | static const uint8_t STATUS_MESSAGE_SIZE_EXCEEDED = 0x0A; 106 | static const uint8_t DISCOVERY_ALREADY_STARTED = 0xA0; 107 | static const uint8_t DISCOVERY_TARGET_ACTIVATION_FAILED = 0xA1; 108 | static const uint8_t DISCOVERY_TEAR_DOWN = 0xA2; 109 | static const uint8_t RF_TRANSMISSION_ERROR = 0xB0; 110 | static const uint8_t RF_PROTOCOL_ERROR = 0xB1; 111 | static const uint8_t RF_TIMEOUT_ERROR = 0xB2; 112 | static const uint8_t NFCEE_INTERFACE_ACTIVATION_FAILED = 0xC0; 113 | static const uint8_t NFCEE_TRANSMISSION_ERROR = 0xC1; 114 | static const uint8_t NFCEE_PROTOCOL_ERROR = 0xC2; 115 | static const uint8_t NFCEE_TIMEOUT_ERROR = 0xC3; 116 | // Deactivation types/reasons 117 | static const uint8_t DEACTIVATION_TYPE_IDLE = 0x00; 118 | static const uint8_t DEACTIVATION_TYPE_SLEEP = 0x01; 119 | static const uint8_t DEACTIVATION_TYPE_SLEEP_AF = 0x02; 120 | static const uint8_t DEACTIVATION_TYPE_DISCOVERY = 0x03; 121 | // RF discover map modes 122 | static const uint8_t RF_DISCOVER_MAP_MODE_POLL = 0x1; 123 | static const uint8_t RF_DISCOVER_MAP_MODE_LISTEN = 0x2; 124 | // RF discover notification types 125 | static const uint8_t RF_DISCOVER_NTF_NT_LAST = 0x00; 126 | static const uint8_t RF_DISCOVER_NTF_NT_LAST_RL = 0x01; 127 | static const uint8_t RF_DISCOVER_NTF_NT_MORE = 0x02; 128 | // Important message offsets 129 | static const uint8_t RF_DISCOVER_NTF_DISCOVERY_ID = 0 + NCI_PKT_HEADER_SIZE; 130 | static const uint8_t RF_DISCOVER_NTF_PROTOCOL = 1 + NCI_PKT_HEADER_SIZE; 131 | static const uint8_t RF_DISCOVER_NTF_MODE_TECH = 2 + NCI_PKT_HEADER_SIZE; 132 | static const uint8_t RF_DISCOVER_NTF_RF_TECH_LENGTH = 3 + NCI_PKT_HEADER_SIZE; 133 | static const uint8_t RF_DISCOVER_NTF_RF_TECH_PARAMS = 4 + NCI_PKT_HEADER_SIZE; 134 | static const uint8_t RF_INTF_ACTIVATED_NTF_DISCOVERY_ID = 0 + NCI_PKT_HEADER_SIZE; 135 | static const uint8_t RF_INTF_ACTIVATED_NTF_INTERFACE = 1 + NCI_PKT_HEADER_SIZE; 136 | static const uint8_t RF_INTF_ACTIVATED_NTF_PROTOCOL = 2 + NCI_PKT_HEADER_SIZE; 137 | static const uint8_t RF_INTF_ACTIVATED_NTF_MODE_TECH = 3 + NCI_PKT_HEADER_SIZE; 138 | static const uint8_t RF_INTF_ACTIVATED_NTF_MAX_SIZE = 4 + NCI_PKT_HEADER_SIZE; 139 | static const uint8_t RF_INTF_ACTIVATED_NTF_INIT_CRED = 5 + NCI_PKT_HEADER_SIZE; 140 | static const uint8_t RF_INTF_ACTIVATED_NTF_RF_TECH_LENGTH = 6 + NCI_PKT_HEADER_SIZE; 141 | static const uint8_t RF_INTF_ACTIVATED_NTF_RF_TECH_PARAMS = 7 + NCI_PKT_HEADER_SIZE; 142 | 143 | } // namespace nfc 144 | } // namespace esphome 145 | -------------------------------------------------------------------------------- /firmware/components/nfc/nci_message.cpp: -------------------------------------------------------------------------------- 1 | #include "nci_core.h" 2 | #include "nci_message.h" 3 | #include "esphome/core/log.h" 4 | 5 | #include 6 | 7 | namespace esphome { 8 | namespace nfc { 9 | 10 | static const char *const TAG = "NciMessage"; 11 | 12 | NciMessage::NciMessage(const uint8_t message_type, const std::vector &payload) { 13 | this->set_message(message_type, payload); 14 | } 15 | 16 | NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid) { 17 | this->set_header(message_type, gid, oid); 18 | } 19 | 20 | NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid, 21 | const std::vector &payload) { 22 | this->set_message(message_type, gid, oid, payload); 23 | } 24 | 25 | NciMessage::NciMessage(const std::vector &raw_packet) { this->nci_message_ = raw_packet; }; 26 | 27 | std::vector NciMessage::encode() { 28 | this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE; 29 | std::vector message = this->nci_message_; 30 | return message; 31 | } 32 | 33 | void NciMessage::reset() { this->nci_message_ = {0, 0, 0}; } 34 | 35 | uint8_t NciMessage::get_message_type() const { 36 | return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK; 37 | } 38 | 39 | uint8_t NciMessage::get_gid() const { return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK; } 40 | 41 | uint8_t NciMessage::get_oid() const { return this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK; } 42 | 43 | uint8_t NciMessage::get_payload_size(const bool recompute) { 44 | if (!this->nci_message_.empty()) { 45 | if (recompute) { 46 | this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE; 47 | } 48 | return this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET]; 49 | } 50 | return 0; 51 | } 52 | 53 | uint8_t NciMessage::get_simple_status_response() const { 54 | if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) { 55 | return this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET]; 56 | } 57 | return STATUS_FAILED; 58 | } 59 | 60 | uint8_t NciMessage::get_message_byte(const uint8_t offset) const { 61 | if (this->nci_message_.size() > offset) { 62 | return this->nci_message_[offset]; 63 | } 64 | return 0; 65 | } 66 | 67 | std::vector &NciMessage::get_message() { return this->nci_message_; } 68 | 69 | bool NciMessage::has_payload() const { return this->nci_message_.size() > nfc::NCI_PKT_HEADER_SIZE; } 70 | 71 | bool NciMessage::message_type_is(const uint8_t message_type) const { 72 | if (!this->nci_message_.empty()) { 73 | return message_type == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK); 74 | } 75 | return false; 76 | } 77 | 78 | bool NciMessage::message_length_is(const uint8_t message_length, const bool recompute) { 79 | if (this->nci_message_.size() > nfc::NCI_PKT_LENGTH_OFFSET) { 80 | if (recompute) { 81 | this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE; 82 | } 83 | return message_length == this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET]; 84 | } 85 | return false; 86 | } 87 | 88 | bool NciMessage::gid_is(const uint8_t gid) const { 89 | if (this->nci_message_.size() > nfc::NCI_PKT_MT_GID_OFFSET) { 90 | return gid == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK); 91 | } 92 | return false; 93 | } 94 | 95 | bool NciMessage::oid_is(const uint8_t oid) const { 96 | if (this->nci_message_.size() > nfc::NCI_PKT_OID_OFFSET) { 97 | return oid == (this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK); 98 | } 99 | return false; 100 | } 101 | 102 | bool NciMessage::simple_status_response_is(const uint8_t response) const { 103 | if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) { 104 | return response == this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET]; 105 | } 106 | return false; 107 | } 108 | 109 | void NciMessage::set_header(const uint8_t message_type, const uint8_t gid, const uint8_t oid) { 110 | if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) { 111 | this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); 112 | } 113 | this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = 114 | (message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK); 115 | this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK; 116 | } 117 | 118 | void NciMessage::set_message(const uint8_t message_type, const std::vector &payload) { 119 | this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); 120 | this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size(); 121 | this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end()); 122 | } 123 | 124 | void NciMessage::set_message(const uint8_t message_type, const uint8_t gid, const uint8_t oid, 125 | const std::vector &payload) { 126 | this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); 127 | this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = 128 | (message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK); 129 | this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK; 130 | this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size(); 131 | this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end()); 132 | } 133 | 134 | void NciMessage::set_message_type(const uint8_t message_type) { 135 | if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) { 136 | this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); 137 | } 138 | auto mt_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_MT_MASK; 139 | this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = mt_masked | (message_type & nfc::NCI_PKT_MT_MASK); 140 | } 141 | 142 | void NciMessage::set_gid(const uint8_t gid) { 143 | if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) { 144 | this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); 145 | } 146 | auto gid_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_GID_MASK; 147 | this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = gid_masked | (gid & nfc::NCI_PKT_GID_MASK); 148 | } 149 | 150 | void NciMessage::set_oid(const uint8_t oid) { 151 | if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) { 152 | this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); 153 | } 154 | this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK; 155 | } 156 | 157 | void NciMessage::set_payload(const std::vector &payload) { 158 | std::vector message(this->nci_message_.begin(), this->nci_message_.begin() + nfc::NCI_PKT_HEADER_SIZE); 159 | 160 | message.insert(message.end(), payload.begin(), payload.end()); 161 | message[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size(); 162 | this->nci_message_ = message; 163 | } 164 | 165 | } // namespace nfc 166 | } // namespace esphome 167 | -------------------------------------------------------------------------------- /firmware/components/nfc/nci_message.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/log.h" 4 | #include "esphome/core/helpers.h" 5 | 6 | #include 7 | 8 | namespace esphome { 9 | namespace nfc { 10 | 11 | class NciMessage { 12 | public: 13 | NciMessage() {} 14 | NciMessage(uint8_t message_type, const std::vector &payload); 15 | NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid); 16 | NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector &payload); 17 | NciMessage(const std::vector &raw_packet); 18 | 19 | std::vector encode(); 20 | void reset(); 21 | 22 | uint8_t get_message_type() const; 23 | uint8_t get_gid() const; 24 | uint8_t get_oid() const; 25 | uint8_t get_payload_size(bool recompute = false); 26 | uint8_t get_simple_status_response() const; 27 | uint8_t get_message_byte(uint8_t offset) const; 28 | std::vector &get_message(); 29 | 30 | bool has_payload() const; 31 | bool message_type_is(uint8_t message_type) const; 32 | bool message_length_is(uint8_t message_length, bool recompute = false); 33 | bool gid_is(uint8_t gid) const; 34 | bool oid_is(uint8_t oid) const; 35 | bool simple_status_response_is(uint8_t response) const; 36 | 37 | void set_header(uint8_t message_type, uint8_t gid, uint8_t oid); 38 | void set_message(uint8_t message_type, const std::vector &payload); 39 | void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector &payload); 40 | void set_message_type(uint8_t message_type); 41 | void set_gid(uint8_t gid); 42 | void set_oid(uint8_t oid); 43 | void set_payload(const std::vector &payload); 44 | 45 | protected: 46 | std::vector nci_message_{0, 0, 0}; // three bytes, MT/PBF/GID, OID, payload length/size 47 | }; 48 | 49 | } // namespace nfc 50 | } // namespace esphome 51 | -------------------------------------------------------------------------------- /firmware/components/nfc/ndef_message.cpp: -------------------------------------------------------------------------------- 1 | #include "ndef_message.h" 2 | #include 3 | 4 | namespace esphome { 5 | namespace nfc { 6 | 7 | static const char *const TAG = "nfc.ndef_message"; 8 | 9 | NdefMessage::NdefMessage(std::vector &data) { 10 | ESP_LOGV(TAG, "Building NdefMessage with %zu bytes", data.size()); 11 | uint8_t index = 0; 12 | while (index <= data.size()) { 13 | uint8_t tnf_byte = data[index++]; 14 | bool me = tnf_byte & 0x40; // Message End bit (is set if this is the last record of the message) 15 | bool sr = tnf_byte & 0x10; // Short record bit (is set if payload size is less or equal to 255 bytes) 16 | bool il = tnf_byte & 0x08; // ID length bit (is set if ID Length field exists) 17 | uint8_t tnf = tnf_byte & 0x07; // Type Name Format 18 | 19 | ESP_LOGVV(TAG, "me=%s, sr=%s, il=%s, tnf=%d", YESNO(me), YESNO(sr), YESNO(il), tnf); 20 | 21 | uint8_t type_length = data[index++]; 22 | uint32_t payload_length = 0; 23 | if (sr) { 24 | payload_length = data[index++]; 25 | } else { 26 | payload_length = (static_cast(data[index]) << 24) | (static_cast(data[index + 1]) << 16) | 27 | (static_cast(data[index + 2]) << 8) | static_cast(data[index + 3]); 28 | index += 4; 29 | } 30 | 31 | uint8_t id_length = 0; 32 | if (il) { 33 | id_length = data[index++]; 34 | } 35 | 36 | ESP_LOGVV(TAG, "Lengths: type=%d, payload=%" PRIu32 ", id=%d", type_length, payload_length, id_length); 37 | 38 | std::string type_str(data.begin() + index, data.begin() + index + type_length); 39 | 40 | index += type_length; 41 | 42 | std::string id_str = ""; 43 | if (il) { 44 | id_str = std::string(data.begin() + index, data.begin() + index + id_length); 45 | index += id_length; 46 | } 47 | 48 | if ((data.begin() + index > data.end()) || (data.begin() + index + payload_length > data.end())) { 49 | ESP_LOGE(TAG, "Corrupt record encountered; NdefMessage constructor aborting"); 50 | break; 51 | } 52 | 53 | std::vector payload_data(data.begin() + index, data.begin() + index + payload_length); 54 | 55 | std::unique_ptr record; 56 | 57 | // Based on tnf and type, create a more specific NdefRecord object 58 | // constructed from the payload data 59 | if (tnf == TNF_WELL_KNOWN && type_str == "U") { 60 | record = make_unique(payload_data); 61 | } else if (tnf == TNF_WELL_KNOWN && type_str == "T") { 62 | record = make_unique(payload_data); 63 | } else { 64 | // Could not recognize the record, so store as generic one. 65 | record = make_unique(payload_data); 66 | record->set_tnf(tnf); 67 | record->set_type(type_str); 68 | } 69 | 70 | record->set_id(id_str); 71 | 72 | index += payload_length; 73 | 74 | ESP_LOGV(TAG, "Adding record type %s = %s", record->get_type().c_str(), record->get_payload().c_str()); 75 | this->add_record(std::move(record)); 76 | 77 | if (me) 78 | break; 79 | } 80 | } 81 | 82 | bool NdefMessage::add_record(std::unique_ptr record) { 83 | if (this->records_.size() >= MAX_NDEF_RECORDS) { 84 | ESP_LOGE(TAG, "Too many records. Max: %d", MAX_NDEF_RECORDS); 85 | return false; 86 | } 87 | this->records_.emplace_back(std::move(record)); 88 | return true; 89 | } 90 | 91 | bool NdefMessage::add_text_record(const std::string &text) { return this->add_text_record(text, "en"); }; 92 | 93 | bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { 94 | return this->add_record(make_unique(encoding, text)); 95 | } 96 | 97 | bool NdefMessage::add_uri_record(const std::string &uri) { return this->add_record(make_unique(uri)); } 98 | 99 | std::vector NdefMessage::encode() { 100 | std::vector data; 101 | 102 | for (size_t i = 0; i < this->records_.size(); i++) { 103 | auto encoded_record = this->records_[i]->encode(i == 0, (i + 1) == this->records_.size()); 104 | data.insert(data.end(), encoded_record.begin(), encoded_record.end()); 105 | } 106 | return data; 107 | } 108 | 109 | } // namespace nfc 110 | } // namespace esphome 111 | -------------------------------------------------------------------------------- /firmware/components/nfc/ndef_message.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "esphome/core/helpers.h" 7 | #include "esphome/core/log.h" 8 | #include "ndef_record.h" 9 | #include "ndef_record_text.h" 10 | #include "ndef_record_uri.h" 11 | 12 | namespace esphome { 13 | namespace nfc { 14 | 15 | static const uint8_t MAX_NDEF_RECORDS = 4; 16 | 17 | class NdefMessage { 18 | public: 19 | NdefMessage() = default; 20 | NdefMessage(std::vector &data); 21 | NdefMessage(const NdefMessage &msg) { 22 | records_.reserve(msg.records_.size()); 23 | for (const auto &r : msg.records_) { 24 | records_.emplace_back(r->clone()); 25 | } 26 | } 27 | 28 | const std::vector> &get_records() { return this->records_; }; 29 | 30 | bool add_record(std::unique_ptr record); 31 | bool add_text_record(const std::string &text); 32 | bool add_text_record(const std::string &text, const std::string &encoding); 33 | bool add_uri_record(const std::string &uri); 34 | 35 | std::vector encode(); 36 | 37 | protected: 38 | std::vector> records_; 39 | }; 40 | 41 | } // namespace nfc 42 | } // namespace esphome 43 | -------------------------------------------------------------------------------- /firmware/components/nfc/ndef_record.cpp: -------------------------------------------------------------------------------- 1 | #include "ndef_record.h" 2 | 3 | namespace esphome { 4 | namespace nfc { 5 | 6 | static const char *const TAG = "nfc.ndef_record"; 7 | 8 | NdefRecord::NdefRecord(std::vector payload_data) { 9 | this->payload_ = std::string(payload_data.begin(), payload_data.end()); 10 | } 11 | 12 | std::vector NdefRecord::encode(bool first, bool last) { 13 | std::vector data; 14 | 15 | // Get encoded payload, this is overridden by more specific record classes 16 | std::vector payload_data = get_encoded_payload(); 17 | 18 | size_t payload_length = payload_data.size(); 19 | 20 | data.push_back(this->create_flag_byte(first, last, payload_length)); 21 | 22 | data.push_back(this->type_.length()); 23 | 24 | if (payload_length <= 255) { 25 | data.push_back(payload_length); 26 | } else { 27 | data.push_back(0); 28 | data.push_back(0); 29 | data.push_back((payload_length >> 8) & 0xFF); 30 | data.push_back(payload_length & 0xFF); 31 | } 32 | 33 | if (this->id_.length()) { 34 | data.push_back(this->id_.length()); 35 | } 36 | 37 | data.insert(data.end(), this->type_.begin(), this->type_.end()); 38 | 39 | if (this->id_.length()) { 40 | data.insert(data.end(), this->id_.begin(), this->id_.end()); 41 | } 42 | 43 | data.insert(data.end(), payload_data.begin(), payload_data.end()); 44 | return data; 45 | } 46 | 47 | uint8_t NdefRecord::create_flag_byte(bool first, bool last, size_t payload_size) { 48 | uint8_t value = this->tnf_ & 0b00000111; 49 | if (first) { 50 | value = value | 0x80; // Set MB bit 51 | } 52 | if (last) { 53 | value = value | 0x40; // Set ME bit 54 | } 55 | if (payload_size <= 255) { 56 | value = value | 0x10; // Set SR bit 57 | } 58 | if (this->id_.length()) { 59 | value = value | 0x08; // Set IL bit 60 | } 61 | return value; 62 | }; 63 | 64 | } // namespace nfc 65 | } // namespace esphome 66 | -------------------------------------------------------------------------------- /firmware/components/nfc/ndef_record.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/log.h" 4 | #include "esphome/core/helpers.h" 5 | 6 | #include 7 | 8 | namespace esphome { 9 | namespace nfc { 10 | 11 | static const uint8_t TNF_EMPTY = 0x00; 12 | static const uint8_t TNF_WELL_KNOWN = 0x01; 13 | static const uint8_t TNF_MIME_MEDIA = 0x02; 14 | static const uint8_t TNF_ABSOLUTE_URI = 0x03; 15 | static const uint8_t TNF_EXTERNAL_TYPE = 0x04; 16 | static const uint8_t TNF_UNKNOWN = 0x05; 17 | static const uint8_t TNF_UNCHANGED = 0x06; 18 | static const uint8_t TNF_RESERVED = 0x07; 19 | 20 | class NdefRecord { 21 | public: 22 | NdefRecord(){}; 23 | NdefRecord(std::vector payload_data); 24 | void set_tnf(uint8_t tnf) { this->tnf_ = tnf; }; 25 | void set_type(const std::string &type) { this->type_ = type; }; 26 | void set_payload(const std::string &payload) { this->payload_ = payload; }; 27 | void set_id(const std::string &id) { this->id_ = id; }; 28 | NdefRecord(const NdefRecord &) = default; 29 | virtual ~NdefRecord() {} 30 | virtual std::unique_ptr clone() const { // To allow copying polymorphic classes 31 | return make_unique(*this); 32 | }; 33 | 34 | uint32_t get_encoded_size(); 35 | 36 | std::vector encode(bool first, bool last); 37 | 38 | uint8_t create_flag_byte(bool first, bool last, size_t payload_size); 39 | 40 | const std::string &get_type() const { return this->type_; }; 41 | const std::string &get_id() const { return this->id_; }; 42 | virtual const std::string &get_payload() const { return this->payload_; }; 43 | 44 | virtual std::vector get_encoded_payload() { 45 | std::vector payload(this->payload_.begin(), this->payload_.end()); 46 | return payload; 47 | }; 48 | 49 | protected: 50 | uint8_t tnf_; 51 | std::string type_; 52 | std::string id_; 53 | std::string payload_; 54 | }; 55 | 56 | } // namespace nfc 57 | } // namespace esphome 58 | -------------------------------------------------------------------------------- /firmware/components/nfc/ndef_record_text.cpp: -------------------------------------------------------------------------------- 1 | #include "ndef_record_text.h" 2 | #include "ndef_record.h" 3 | 4 | namespace esphome { 5 | namespace nfc { 6 | 7 | static const char *const TAG = "nfc.ndef_record_text"; 8 | 9 | NdefRecordText::NdefRecordText(const std::vector &payload) { 10 | if (payload.empty()) { 11 | ESP_LOGE(TAG, "Record payload too short"); 12 | return; 13 | } 14 | 15 | uint8_t language_code_length = payload[0] & 0b00111111; // Todo, make use of encoding bit? 16 | 17 | this->language_code_ = std::string(payload.begin() + 1, payload.begin() + 1 + language_code_length); 18 | 19 | this->text_ = std::string(payload.begin() + 1 + language_code_length, payload.end()); 20 | 21 | this->tnf_ = TNF_WELL_KNOWN; 22 | 23 | this->type_ = "T"; 24 | } 25 | 26 | std::vector NdefRecordText::get_encoded_payload() { 27 | std::vector data; 28 | 29 | uint8_t flag_byte = this->language_code_.length() & 0b00111111; // UTF8 assumed 30 | 31 | data.push_back(flag_byte); 32 | 33 | data.insert(data.end(), this->language_code_.begin(), this->language_code_.end()); 34 | 35 | data.insert(data.end(), this->text_.begin(), this->text_.end()); 36 | return data; 37 | } 38 | 39 | } // namespace nfc 40 | } // namespace esphome 41 | -------------------------------------------------------------------------------- /firmware/components/nfc/ndef_record_text.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/log.h" 4 | #include "esphome/core/helpers.h" 5 | #include "ndef_record.h" 6 | 7 | #include 8 | 9 | namespace esphome { 10 | namespace nfc { 11 | 12 | class NdefRecordText : public NdefRecord { 13 | public: 14 | NdefRecordText(){}; 15 | NdefRecordText(const std::vector &payload); 16 | NdefRecordText(const std::string &language_code, const std::string &text) { 17 | this->tnf_ = TNF_WELL_KNOWN; 18 | this->type_ = "T"; 19 | this->language_code_ = language_code; 20 | this->text_ = text; 21 | }; 22 | NdefRecordText(const std::string &language_code, const std::string &text, const std::string &id) { 23 | this->tnf_ = TNF_WELL_KNOWN; 24 | this->type_ = "T"; 25 | this->language_code_ = language_code; 26 | this->text_ = text; 27 | this->id_ = id; 28 | }; 29 | NdefRecordText(const NdefRecordText &) = default; 30 | 31 | std::unique_ptr clone() const override { return make_unique(*this); }; 32 | 33 | std::vector get_encoded_payload() override; 34 | 35 | const std::string &get_payload() const override { return this->text_; }; 36 | 37 | protected: 38 | std::string text_; 39 | std::string language_code_; 40 | }; 41 | 42 | } // namespace nfc 43 | } // namespace esphome 44 | -------------------------------------------------------------------------------- /firmware/components/nfc/ndef_record_uri.cpp: -------------------------------------------------------------------------------- 1 | #include "ndef_record_uri.h" 2 | 3 | namespace esphome { 4 | namespace nfc { 5 | 6 | static const char *const TAG = "nfc.ndef_record_uri"; 7 | 8 | NdefRecordUri::NdefRecordUri(const std::vector &payload) { 9 | if (payload.empty()) { 10 | ESP_LOGE(TAG, "Record payload too short"); 11 | return; 12 | } 13 | 14 | uint8_t payload_identifier = payload[0]; // First byte of payload is prefix code 15 | 16 | std::string uri(payload.begin() + 1, payload.end()); 17 | 18 | if (payload_identifier > 0x00 && payload_identifier <= PAYLOAD_IDENTIFIERS_COUNT) { 19 | uri.insert(0, PAYLOAD_IDENTIFIERS[payload_identifier]); 20 | } 21 | 22 | this->tnf_ = TNF_WELL_KNOWN; 23 | this->type_ = "U"; 24 | this->set_uri(uri); 25 | } 26 | 27 | std::vector NdefRecordUri::get_encoded_payload() { 28 | std::vector data; 29 | 30 | uint8_t payload_prefix = 0x00; 31 | uint8_t payload_prefix_length = 0x00; 32 | for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { 33 | std::string prefix = PAYLOAD_IDENTIFIERS[i]; 34 | if (this->uri_.substr(0, prefix.length()).find(prefix) != std::string::npos) { 35 | payload_prefix = i; 36 | payload_prefix_length = prefix.length(); 37 | break; 38 | } 39 | } 40 | 41 | data.push_back(payload_prefix); 42 | 43 | data.insert(data.end(), this->uri_.begin() + payload_prefix_length, this->uri_.end()); 44 | return data; 45 | } 46 | 47 | } // namespace nfc 48 | } // namespace esphome 49 | -------------------------------------------------------------------------------- /firmware/components/nfc/ndef_record_uri.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/log.h" 4 | #include "esphome/core/helpers.h" 5 | #include "ndef_record.h" 6 | 7 | #include 8 | 9 | namespace esphome { 10 | namespace nfc { 11 | 12 | static const uint8_t PAYLOAD_IDENTIFIERS_COUNT = 0x23; 13 | static const char *const PAYLOAD_IDENTIFIERS[] = {"", 14 | "http://www.", 15 | "https://www.", 16 | "http://", 17 | "https://", 18 | "tel:", 19 | "mailto:", 20 | "ftp://anonymous:anonymous@", 21 | "ftp://ftp.", 22 | "ftps://", 23 | "sftp://", 24 | "smb://", 25 | "nfs://", 26 | "ftp://", 27 | "dav://", 28 | "news:", 29 | "telnet://", 30 | "imap:", 31 | "rtsp://", 32 | "urn:", 33 | "pop:", 34 | "sip:", 35 | "sips:", 36 | "tftp:", 37 | "btspp://", 38 | "btl2cap://", 39 | "btgoep://", 40 | "tcpobex://", 41 | "irdaobex://", 42 | "file://", 43 | "urn:epc:id:", 44 | "urn:epc:tag:", 45 | "urn:epc:pat:", 46 | "urn:epc:raw:", 47 | "urn:epc:", 48 | "urn:nfc:"}; 49 | 50 | class NdefRecordUri : public NdefRecord { 51 | public: 52 | NdefRecordUri(){}; 53 | NdefRecordUri(const std::vector &payload); 54 | NdefRecordUri(const std::string &uri) { 55 | this->tnf_ = TNF_WELL_KNOWN; 56 | this->type_ = "U"; 57 | this->uri_ = uri; 58 | }; 59 | NdefRecordUri(const std::string &uri, const std::string &id) { 60 | this->tnf_ = TNF_WELL_KNOWN; 61 | this->type_ = "U"; 62 | this->uri_ = uri; 63 | this->id_ = id; 64 | }; 65 | NdefRecordUri(const NdefRecordUri &) = default; 66 | std::unique_ptr clone() const override { return make_unique(*this); }; 67 | 68 | void set_uri(const std::string &uri) { this->uri_ = uri; }; 69 | 70 | std::vector get_encoded_payload() override; 71 | const std::string &get_payload() const override { return this->uri_; }; 72 | 73 | protected: 74 | std::string uri_; 75 | }; 76 | 77 | } // namespace nfc 78 | } // namespace esphome 79 | -------------------------------------------------------------------------------- /firmware/components/nfc/nfc.cpp: -------------------------------------------------------------------------------- 1 | #include "nfc.h" 2 | #include 3 | #include "esphome/core/log.h" 4 | 5 | namespace esphome { 6 | namespace nfc { 7 | 8 | static const char *const TAG = "nfc"; 9 | 10 | std::string format_uid(std::vector &uid) { 11 | char buf[(uid.size() * 2) + uid.size() - 1]; 12 | int offset = 0; 13 | for (size_t i = 0; i < uid.size(); i++) { 14 | const char *format = "%02X"; 15 | if (i + 1 < uid.size()) 16 | format = "%02X-"; 17 | offset += sprintf(buf + offset, format, uid[i]); 18 | } 19 | return std::string(buf); 20 | } 21 | 22 | std::string format_bytes(std::vector &bytes) { 23 | char buf[(bytes.size() * 2) + bytes.size() - 1]; 24 | int offset = 0; 25 | for (size_t i = 0; i < bytes.size(); i++) { 26 | const char *format = "%02X"; 27 | if (i + 1 < bytes.size()) 28 | format = "%02X "; 29 | offset += sprintf(buf + offset, format, bytes[i]); 30 | } 31 | return std::string(buf); 32 | } 33 | 34 | uint8_t guess_tag_type(uint8_t uid_length) { 35 | if (uid_length == 4) { 36 | return TAG_TYPE_MIFARE_CLASSIC; 37 | } else { 38 | return TAG_TYPE_2; 39 | } 40 | } 41 | 42 | uint8_t get_mifare_classic_ndef_start_index(std::vector &data) { 43 | for (uint8_t i = 0; i < MIFARE_CLASSIC_BLOCK_SIZE; i++) { 44 | if (data[i] == 0x00) { 45 | // Do nothing, skip 46 | } else if (data[i] == 0x03) { 47 | return i; 48 | } else { 49 | return -2; 50 | } 51 | } 52 | return -1; 53 | } 54 | 55 | bool decode_mifare_classic_tlv(std::vector &data, uint32_t &message_length, uint8_t &message_start_index) { 56 | auto i = get_mifare_classic_ndef_start_index(data); 57 | if (data[i] != 0x03) { 58 | ESP_LOGE(TAG, "Error, Can't decode message length."); 59 | return false; 60 | } 61 | if (data[i + 1] == 0xFF) { 62 | message_length = ((0xFF & data[i + 2]) << 8) | (0xFF & data[i + 3]); 63 | message_start_index = i + MIFARE_CLASSIC_LONG_TLV_SIZE; 64 | } else { 65 | message_length = data[i + 1]; 66 | message_start_index = i + MIFARE_CLASSIC_SHORT_TLV_SIZE; 67 | } 68 | return true; 69 | } 70 | 71 | uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length) { 72 | uint32_t buffer_size = message_length + 2 + 1; 73 | if (buffer_size % MIFARE_ULTRALIGHT_READ_SIZE != 0) 74 | buffer_size = ((buffer_size / MIFARE_ULTRALIGHT_READ_SIZE) + 1) * MIFARE_ULTRALIGHT_READ_SIZE; 75 | return buffer_size; 76 | } 77 | 78 | uint32_t get_mifare_classic_buffer_size(uint32_t message_length) { 79 | uint32_t buffer_size = message_length; 80 | if (message_length < 255) { 81 | buffer_size += MIFARE_CLASSIC_SHORT_TLV_SIZE + 1; 82 | } else { 83 | buffer_size += MIFARE_CLASSIC_LONG_TLV_SIZE + 1; 84 | } 85 | if (buffer_size % MIFARE_CLASSIC_BLOCK_SIZE != 0) { 86 | buffer_size = ((buffer_size / MIFARE_CLASSIC_BLOCK_SIZE) + 1) * MIFARE_CLASSIC_BLOCK_SIZE; 87 | } 88 | return buffer_size; 89 | } 90 | 91 | bool mifare_classic_is_first_block(uint8_t block_num) { 92 | if (block_num < MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * MIFARE_CLASSIC_16BLOCK_SECT_START) { 93 | return (block_num % MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW == 0); 94 | } else { 95 | return (block_num % MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH == 0); 96 | } 97 | } 98 | 99 | bool mifare_classic_is_trailer_block(uint8_t block_num) { 100 | if (block_num < MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * MIFARE_CLASSIC_16BLOCK_SECT_START) { 101 | return ((block_num + 1) % MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW == 0); 102 | } else { 103 | return ((block_num + 1) % MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH == 0); 104 | } 105 | } 106 | 107 | } // namespace nfc 108 | } // namespace esphome 109 | -------------------------------------------------------------------------------- /firmware/components/nfc/nfc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/log.h" 4 | #include "esphome/core/helpers.h" 5 | #include "ndef_record.h" 6 | #include "ndef_message.h" 7 | #include "nfc_tag.h" 8 | 9 | #include 10 | 11 | namespace esphome { 12 | namespace nfc { 13 | 14 | static const uint8_t MIFARE_CLASSIC_BLOCK_SIZE = 16; 15 | static const uint8_t MIFARE_CLASSIC_LONG_TLV_SIZE = 4; 16 | static const uint8_t MIFARE_CLASSIC_SHORT_TLV_SIZE = 2; 17 | static const uint8_t MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW = 4; 18 | static const uint8_t MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH = 16; 19 | static const uint8_t MIFARE_CLASSIC_16BLOCK_SECT_START = 32; 20 | 21 | static const uint8_t MIFARE_ULTRALIGHT_PAGE_SIZE = 4; 22 | static const uint8_t MIFARE_ULTRALIGHT_READ_SIZE = 4; 23 | static const uint8_t MIFARE_ULTRALIGHT_DATA_START_PAGE = 4; 24 | static const uint8_t MIFARE_ULTRALIGHT_MAX_PAGE = 63; 25 | 26 | static const uint8_t TAG_TYPE_MIFARE_CLASSIC = 0; 27 | static const uint8_t TAG_TYPE_1 = 1; 28 | static const uint8_t TAG_TYPE_2 = 2; 29 | static const uint8_t TAG_TYPE_3 = 3; 30 | static const uint8_t TAG_TYPE_4 = 4; 31 | static const uint8_t TAG_TYPE_UNKNOWN = 99; 32 | 33 | // Mifare Commands 34 | static const uint8_t MIFARE_CMD_AUTH_A = 0x60; 35 | static const uint8_t MIFARE_CMD_AUTH_B = 0x61; 36 | static const uint8_t MIFARE_CMD_HALT = 0x50; 37 | static const uint8_t MIFARE_CMD_READ = 0x30; 38 | static const uint8_t MIFARE_CMD_WRITE = 0xA0; 39 | static const uint8_t MIFARE_CMD_WRITE_ULTRALIGHT = 0xA2; 40 | 41 | // Mifare Ack/Nak 42 | static const uint8_t MIFARE_CMD_ACK = 0x0A; 43 | static const uint8_t MIFARE_CMD_NAK_INVALID_XFER_BUFF_VALID = 0x00; 44 | static const uint8_t MIFARE_CMD_NAK_CRC_ERROR_XFER_BUFF_VALID = 0x01; 45 | static const uint8_t MIFARE_CMD_NAK_INVALID_XFER_BUFF_INVALID = 0x04; 46 | static const uint8_t MIFARE_CMD_NAK_CRC_ERROR_XFER_BUFF_INVALID = 0x05; 47 | 48 | static const char *const MIFARE_CLASSIC = "Mifare Classic"; 49 | static const char *const NFC_FORUM_TYPE_2 = "NFC Forum Type 2"; 50 | static const char *const ERROR = "Error"; 51 | 52 | static const uint8_t DEFAULT_KEY[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 53 | static const uint8_t NDEF_KEY[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7}; 54 | static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5}; 55 | 56 | std::string format_uid(std::vector &uid); 57 | std::string format_bytes(std::vector &bytes); 58 | 59 | uint8_t guess_tag_type(uint8_t uid_length); 60 | uint8_t get_mifare_classic_ndef_start_index(std::vector &data); 61 | bool decode_mifare_classic_tlv(std::vector &data, uint32_t &message_length, uint8_t &message_start_index); 62 | uint32_t get_mifare_classic_buffer_size(uint32_t message_length); 63 | 64 | bool mifare_classic_is_first_block(uint8_t block_num); 65 | bool mifare_classic_is_trailer_block(uint8_t block_num); 66 | 67 | uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length); 68 | 69 | class NfcTagListener { 70 | public: 71 | virtual void tag_off(NfcTag &tag) {} 72 | virtual void tag_on(NfcTag &tag) {} 73 | }; 74 | 75 | class Nfcc { 76 | public: 77 | void register_listener(NfcTagListener *listener) { this->tag_listeners_.push_back(listener); } 78 | 79 | protected: 80 | std::vector tag_listeners_; 81 | }; 82 | 83 | } // namespace nfc 84 | } // namespace esphome 85 | -------------------------------------------------------------------------------- /firmware/components/nfc/nfc_helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "nfc_helpers.h" 2 | 3 | namespace esphome { 4 | namespace nfc { 5 | 6 | static const char *const TAG = "nfc.helpers"; 7 | 8 | bool has_ha_tag_ndef(NfcTag &tag) { return !get_ha_tag_ndef(tag).empty(); } 9 | 10 | std::string get_ha_tag_ndef(NfcTag &tag) { 11 | if (!tag.has_ndef_message()) { 12 | return std::string(); 13 | } 14 | auto message = tag.get_ndef_message(); 15 | auto records = message->get_records(); 16 | for (const auto &record : records) { 17 | std::string payload = record->get_payload(); 18 | size_t pos = payload.find(HA_TAG_ID_PREFIX); 19 | if (pos != std::string::npos) { 20 | return payload.substr(pos + sizeof(HA_TAG_ID_PREFIX) - 1); 21 | } 22 | } 23 | return std::string(); 24 | } 25 | 26 | std::string get_random_ha_tag_ndef() { 27 | static const char ALPHANUM[] = "0123456789abcdef"; 28 | std::string uri = HA_TAG_ID_PREFIX; 29 | for (int i = 0; i < 8; i++) { 30 | uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)]; 31 | } 32 | uri += "-"; 33 | for (int j = 0; j < 3; j++) { 34 | for (int i = 0; i < 4; i++) { 35 | uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)]; 36 | } 37 | uri += "-"; 38 | } 39 | for (int i = 0; i < 12; i++) { 40 | uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)]; 41 | } 42 | ESP_LOGD("pn7160", "Payload to be written: %s", uri.c_str()); 43 | return uri; 44 | } 45 | 46 | } // namespace nfc 47 | } // namespace esphome 48 | -------------------------------------------------------------------------------- /firmware/components/nfc/nfc_helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nfc_tag.h" 4 | 5 | namespace esphome { 6 | namespace nfc { 7 | 8 | static const char HA_TAG_ID_EXT_RECORD_TYPE[] = "android.com:pkg"; 9 | static const char HA_TAG_ID_EXT_RECORD_PAYLOAD[] = "io.homeassistant.companion.android"; 10 | static const char HA_TAG_ID_PREFIX[] = "https://www.home-assistant.io/tag/"; 11 | 12 | std::string get_ha_tag_ndef(NfcTag &tag); 13 | std::string get_random_ha_tag_ndef(); 14 | bool has_ha_tag_ndef(NfcTag &tag); 15 | 16 | } // namespace nfc 17 | } // namespace esphome 18 | -------------------------------------------------------------------------------- /firmware/components/nfc/nfc_tag.cpp: -------------------------------------------------------------------------------- 1 | #include "nfc_tag.h" 2 | 3 | namespace esphome { 4 | namespace nfc { 5 | 6 | static const char *const TAG = "nfc.tag"; 7 | 8 | } // namespace nfc 9 | } // namespace esphome 10 | -------------------------------------------------------------------------------- /firmware/components/nfc/nfc_tag.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "esphome/core/log.h" 7 | #include "esphome/core/helpers.h" 8 | #include "ndef_message.h" 9 | 10 | namespace esphome { 11 | namespace nfc { 12 | 13 | class NfcTag { 14 | public: 15 | NfcTag() { 16 | this->uid_ = {}; 17 | this->tag_type_ = "Unknown"; 18 | }; 19 | NfcTag(std::vector &uid) { 20 | this->uid_ = uid; 21 | this->tag_type_ = "Unknown"; 22 | }; 23 | NfcTag(std::vector &uid, const std::string &tag_type) { 24 | this->uid_ = uid; 25 | this->tag_type_ = tag_type; 26 | }; 27 | NfcTag(std::vector &uid, const std::string &tag_type, std::unique_ptr ndef_message) { 28 | this->uid_ = uid; 29 | this->tag_type_ = tag_type; 30 | this->ndef_message_ = std::move(ndef_message); 31 | }; 32 | NfcTag(std::vector &uid, const std::string &tag_type, std::vector &ndef_data) { 33 | this->uid_ = uid; 34 | this->tag_type_ = tag_type; 35 | this->ndef_message_ = make_unique(ndef_data); 36 | }; 37 | NfcTag(const NfcTag &rhs) { 38 | uid_ = rhs.uid_; 39 | tag_type_ = rhs.tag_type_; 40 | if (rhs.ndef_message_ != nullptr) 41 | ndef_message_ = make_unique(*rhs.ndef_message_); 42 | } 43 | 44 | std::vector &get_uid() { return this->uid_; }; 45 | const std::string &get_tag_type() { return this->tag_type_; }; 46 | bool has_ndef_message() { return this->ndef_message_ != nullptr; }; 47 | const std::shared_ptr &get_ndef_message() { return this->ndef_message_; }; 48 | void set_ndef_message(std::unique_ptr ndef_message) { this->ndef_message_ = std::move(ndef_message); }; 49 | 50 | protected: 51 | std::vector uid_; 52 | std::string tag_type_; 53 | std::shared_ptr ndef_message_; 54 | }; 55 | 56 | } // namespace nfc 57 | } // namespace esphome 58 | -------------------------------------------------------------------------------- /firmware/components/pn532/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | import esphome.config_validation as cv 3 | from esphome import automation 4 | from esphome.components import nfc 5 | from esphome.const import ( 6 | CONF_ID, 7 | CONF_ON_FINISHED_WRITE, 8 | CONF_ON_TAG_REMOVED, 9 | CONF_ON_TAG, 10 | CONF_TRIGGER_ID, 11 | ) 12 | 13 | CODEOWNERS = ["@OttoWinter", "@jesserockz"] 14 | AUTO_LOAD = ["binary_sensor", "nfc"] 15 | MULTI_CONF = True 16 | 17 | CONF_PN532_ID = "pn532_id" 18 | 19 | pn532_ns = cg.esphome_ns.namespace("pn532") 20 | PN532 = pn532_ns.class_("PN532", cg.PollingComponent) 21 | 22 | PN532OnFinishedWriteTrigger = pn532_ns.class_( 23 | "PN532OnFinishedWriteTrigger", automation.Trigger.template() 24 | ) 25 | 26 | PN532IsWritingCondition = pn532_ns.class_( 27 | "PN532IsWritingCondition", automation.Condition 28 | ) 29 | 30 | PN532_SCHEMA = cv.Schema( 31 | { 32 | cv.GenerateID(): cv.declare_id(PN532), 33 | cv.Optional(CONF_ON_TAG): automation.validate_automation( 34 | { 35 | cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), 36 | } 37 | ), 38 | cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( 39 | { 40 | cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( 41 | PN532OnFinishedWriteTrigger 42 | ), 43 | } 44 | ), 45 | cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation( 46 | { 47 | cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), 48 | } 49 | ), 50 | } 51 | ).extend(cv.polling_component_schema("1s")) 52 | 53 | 54 | def CONFIG_SCHEMA(conf): 55 | if conf: 56 | raise cv.Invalid( 57 | "This component has been moved in 1.16, please see the docs for updated " 58 | "instructions. https://esphome.io/components/binary_sensor/pn532.html" 59 | ) 60 | 61 | 62 | async def setup_pn532(var, config): 63 | await cg.register_component(var, config) 64 | 65 | for conf in config.get(CONF_ON_TAG, []): 66 | trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) 67 | cg.add(var.register_ontag_trigger(trigger)) 68 | await automation.build_automation( 69 | trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf 70 | ) 71 | 72 | for conf in config.get(CONF_ON_TAG_REMOVED, []): 73 | trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) 74 | cg.add(var.register_ontagremoved_trigger(trigger)) 75 | await automation.build_automation( 76 | trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf 77 | ) 78 | 79 | for conf in config.get(CONF_ON_FINISHED_WRITE, []): 80 | trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 81 | await automation.build_automation(trigger, [], conf) 82 | 83 | 84 | @automation.register_condition( 85 | "pn532.is_writing", 86 | PN532IsWritingCondition, 87 | cv.Schema( 88 | { 89 | cv.GenerateID(): cv.use_id(PN532), 90 | } 91 | ), 92 | ) 93 | async def pn532_is_writing_to_code(config, condition_id, template_arg, args): 94 | var = cg.new_Pvariable(condition_id, template_arg) 95 | await cg.register_parented(var, config[CONF_ID]) 96 | return var 97 | -------------------------------------------------------------------------------- /firmware/components/pn532/binary_sensor.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | import esphome.config_validation as cv 3 | from esphome.components import binary_sensor 4 | from esphome.const import CONF_UID 5 | from esphome.core import HexInt 6 | from . import pn532_ns, PN532, CONF_PN532_ID 7 | 8 | DEPENDENCIES = ["pn532"] 9 | 10 | 11 | def validate_uid(value): 12 | value = cv.string_strict(value) 13 | for x in value.split("-"): 14 | if len(x) != 2: 15 | raise cv.Invalid( 16 | "Each part (separated by '-') of the UID must be two characters " 17 | "long." 18 | ) 19 | try: 20 | x = int(x, 16) 21 | except ValueError as err: 22 | raise cv.Invalid( 23 | "Valid characters for parts of a UID are 0123456789ABCDEF." 24 | ) from err 25 | if x < 0 or x > 255: 26 | raise cv.Invalid( 27 | "Valid values for UID parts (separated by '-') are 00 to FF" 28 | ) 29 | return value 30 | 31 | 32 | PN532BinarySensor = pn532_ns.class_("PN532BinarySensor", binary_sensor.BinarySensor) 33 | 34 | CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(PN532BinarySensor).extend( 35 | { 36 | cv.GenerateID(CONF_PN532_ID): cv.use_id(PN532), 37 | cv.Required(CONF_UID): validate_uid, 38 | } 39 | ) 40 | 41 | 42 | async def to_code(config): 43 | var = await binary_sensor.new_binary_sensor(config) 44 | 45 | hub = await cg.get_variable(config[CONF_PN532_ID]) 46 | cg.add(hub.register_tag(var)) 47 | addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")] 48 | cg.add(var.set_uid(addr)) 49 | -------------------------------------------------------------------------------- /firmware/components/pn532/pn532.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/core/automation.h" 5 | #include "esphome/components/binary_sensor/binary_sensor.h" 6 | #include "esphome/components/nfc/nfc_tag.h" 7 | #include "esphome/components/nfc/nfc.h" 8 | #include "esphome/components/nfc/automation.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace esphome { 14 | namespace pn532 { 15 | 16 | static const uint8_t PN532_COMMAND_VERSION_DATA = 0x02; 17 | static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14; 18 | static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32; 19 | static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; 20 | static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; 21 | static const uint8_t PN532_COMMAND_POWERDOWN = 0x16; 22 | 23 | enum PN532ReadReady { 24 | WOULDBLOCK = 0, 25 | TIMEOUT, 26 | READY, 27 | }; 28 | 29 | class PN532BinarySensor; 30 | 31 | class PN532 : public PollingComponent { 32 | public: 33 | void setup() override; 34 | 35 | void dump_config() override; 36 | 37 | void update() override; 38 | float get_setup_priority() const override; 39 | 40 | void loop() override; 41 | void on_shutdown() override { powerdown(); } 42 | 43 | void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); } 44 | void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } 45 | void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } 46 | 47 | void add_on_finished_write_callback(std::function callback) { 48 | this->on_finished_write_callback_.add(std::move(callback)); 49 | } 50 | 51 | bool is_writing() { return this->next_task_ != READ; }; 52 | 53 | void read_mode(); 54 | void clean_mode(); 55 | void format_mode(); 56 | void write_mode(nfc::NdefMessage *message); 57 | bool powerdown(); 58 | 59 | protected: 60 | void turn_off_rf_(); 61 | bool write_command_(const std::vector &data); 62 | bool read_ack_(); 63 | void send_ack_(); 64 | void send_nack_(); 65 | 66 | enum PN532ReadReady read_ready_(bool block); 67 | virtual bool is_read_ready() = 0; 68 | virtual bool write_data(const std::vector &data) = 0; 69 | virtual bool read_data(std::vector &data, uint8_t len) = 0; 70 | virtual bool read_response(uint8_t command, std::vector &data) = 0; 71 | 72 | std::unique_ptr read_tag_(std::vector &uid); 73 | 74 | bool format_tag_(std::vector &uid); 75 | bool clean_tag_(std::vector &uid); 76 | bool write_tag_(std::vector &uid, nfc::NdefMessage *message); 77 | 78 | std::unique_ptr read_mifare_classic_tag_(std::vector &uid); 79 | bool read_mifare_classic_block_(uint8_t block_num, std::vector &data); 80 | bool write_mifare_classic_block_(uint8_t block_num, std::vector &data); 81 | bool auth_mifare_classic_block_(std::vector &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key); 82 | bool format_mifare_classic_mifare_(std::vector &uid); 83 | bool format_mifare_classic_ndef_(std::vector &uid); 84 | bool write_mifare_classic_tag_(std::vector &uid, nfc::NdefMessage *message); 85 | 86 | std::unique_ptr read_mifare_ultralight_tag_(std::vector &uid); 87 | bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data); 88 | bool is_mifare_ultralight_formatted_(const std::vector &page_3_to_6); 89 | uint16_t read_mifare_ultralight_capacity_(); 90 | bool find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, 91 | uint8_t &message_start_index); 92 | bool write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); 93 | bool write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMessage *message); 94 | bool clean_mifare_ultralight_(); 95 | 96 | bool updates_enabled_{true}; 97 | bool requested_read_{false}; 98 | std::vector binary_sensors_; 99 | std::vector triggers_ontag_; 100 | std::vector triggers_ontagremoved_; 101 | std::vector current_uid_; 102 | nfc::NdefMessage *next_task_message_to_write_; 103 | uint32_t rd_start_time_{0}; 104 | enum PN532ReadReady rd_ready_ { WOULDBLOCK }; 105 | enum NfcTask { 106 | READ = 0, 107 | CLEAN, 108 | FORMAT, 109 | WRITE, 110 | } next_task_{READ}; 111 | enum PN532Error { 112 | NONE = 0, 113 | WAKEUP_FAILED, 114 | SAM_COMMAND_FAILED, 115 | } error_code_{NONE}; 116 | CallbackManager on_finished_write_callback_; 117 | }; 118 | 119 | class PN532BinarySensor : public binary_sensor::BinarySensor { 120 | public: 121 | void set_uid(const std::vector &uid) { uid_ = uid; } 122 | 123 | bool process(std::vector &data); 124 | 125 | void on_scan_end() { 126 | if (!this->found_) { 127 | this->publish_state(false); 128 | } 129 | this->found_ = false; 130 | } 131 | 132 | protected: 133 | std::vector uid_; 134 | bool found_{false}; 135 | }; 136 | 137 | class PN532OnFinishedWriteTrigger : public Trigger<> { 138 | public: 139 | explicit PN532OnFinishedWriteTrigger(PN532 *parent) { 140 | parent->add_on_finished_write_callback([this]() { this->trigger(); }); 141 | } 142 | }; 143 | 144 | template class PN532IsWritingCondition : public Condition, public Parented { 145 | public: 146 | bool check(Ts... x) override { return this->parent_->is_writing(); } 147 | }; 148 | 149 | } // namespace pn532 150 | } // namespace esphome 151 | -------------------------------------------------------------------------------- /firmware/conf.d/.gitignore: -------------------------------------------------------------------------------- 1 | secrets.yaml 2 | ./secrets.yaml 3 | -------------------------------------------------------------------------------- /firmware/conf.d/api.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | reboot_timeout: 0s #reboot_timeout must be 0 if api and mqtt are defined 3 | id: api_openspool 4 | -------------------------------------------------------------------------------- /firmware/conf.d/bambu_printer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | select: 3 | #TODO: printer model is currently unused, but will be needed for setting AMS type 4 | - platform: template 5 | name: Printer Model 6 | id: bambu_model 7 | state_topic: 8 | options: 9 | - "A1Mini" 10 | - "A1" 11 | - "P1P/P1S" 12 | - "X1C/X1E" 13 | optimistic: true 14 | restore_value: true 15 | icon: mdi:printer-3d 16 | web_server: 17 | sorting_group_id: sorting_group_printer_settings 18 | 19 | text: 20 | - platform: template 21 | name: Printer Serial Number 22 | id: bambu_serial_number 23 | state_topic: 24 | optimistic: true 25 | mode: text 26 | restore_value: true 27 | icon: mdi:barcode 28 | web_server: 29 | sorting_group_id: sorting_group_printer_settings 30 | on_value: 31 | then: 32 | - if: 33 | condition: 34 | lambda: |- 35 | return !id(bambu_lan_access_code).state.empty() && 36 | !id(bambu_ip_address).state.empty() && 37 | !id(bambu_serial_number).state.empty(); 38 | then: 39 | - logger.log: 40 | level: info 41 | format: "Connecting to Bambu printer" 42 | - mqtt.enable: 43 | id: bambu_mqtt 44 | else: 45 | - logger.log: 46 | level: info 47 | format: "Missing Bambu Credentials, skipping mqtt connect" 48 | - mqtt.disable: 49 | id: bambu_mqtt 50 | # - script.execute: check_mqtt_creds #TODO: Enable once this is merged: https://github.com/esphome/esphome/pull/7716 51 | # lambda: |- 52 | # id(bambu_printer_status).topic = "device/" + x + "/report"; 53 | - platform: template 54 | name: Printer Lan Access Code 55 | id: bambu_lan_access_code 56 | state_topic: 57 | optimistic: true 58 | mode: password 59 | restore_value: true 60 | icon: mdi:lock 61 | web_server: 62 | sorting_group_id: sorting_group_printer_settings 63 | on_value: 64 | then: 65 | - lambda: |- 66 | id(bambu_mqtt).set_password(x.c_str()); 67 | - if: 68 | condition: 69 | lambda: |- 70 | return !id(bambu_lan_access_code).state.empty() && 71 | !id(bambu_ip_address).state.empty() && 72 | !id(bambu_serial_number).state.empty(); 73 | then: 74 | - logger.log: 75 | level: info 76 | format: "Connecting to Bambu printer" 77 | - mqtt.enable: 78 | id: bambu_mqtt 79 | else: 80 | - logger.log: 81 | level: info 82 | format: "Missing Bambu Credentials, skipping mqtt connect" 83 | - mqtt.disable: 84 | id: bambu_mqtt 85 | - platform: template 86 | name: Printer IP Address 87 | id: bambu_ip_address 88 | state_topic: 89 | optimistic: true 90 | mode: text 91 | restore_value: true 92 | icon: mdi:ip 93 | web_server: 94 | sorting_group_id: sorting_group_printer_settings 95 | on_value: 96 | then: 97 | - lambda: |- 98 | id(bambu_mqtt).set_broker_address(x.c_str()); 99 | - if: 100 | condition: 101 | lambda: |- 102 | return !id(bambu_lan_access_code).state.empty() && 103 | !id(bambu_ip_address).state.empty() && 104 | !id(bambu_serial_number).state.empty(); 105 | then: 106 | - logger.log: 107 | level: info 108 | format: "Connecting to Bambu printer" 109 | - mqtt.enable: 110 | id: bambu_mqtt 111 | else: 112 | - logger.log: "Missing Bambu Credentials, skipping mqtt connect" 113 | - mqtt.disable: 114 | id: bambu_mqtt 115 | 116 | button: 117 | - platform: restart 118 | name: "Restart OpenSpool" 119 | id: restart_openspool 120 | state_topic: 121 | entity_category: diagnostic 122 | 123 | #TODO: Show the current state of the printer 124 | 125 | # ACTION_IDS = { 126 | # "default": "Unknown", 127 | # -1: "Idle", 128 | # 0: "Printing", 129 | # 1: "Auto Bed Leveling", 130 | # 2: "Heatbed Preheating", 131 | # 3: "Sweeping XY Mech Mode", 132 | # 4: "Changing Filament", 133 | # 5: "M400 Pause", 134 | # 6: "Paused due to filament runout", 135 | # 7: "Heating Hotend", 136 | # 8: "Calibrating Extrusion", 137 | # 9: "Scanning Bed Surface", 138 | # 10: "Inspecting First Layer", 139 | # 11: "Identifying Build Plate Type", 140 | # 12: "Calibrating Micro Lidar", 141 | # 13: "Homing Toolhead", 142 | # 14: "Cleaning Nozzle Tip", 143 | # 15: "Checking Extruder Temperature", 144 | # 16: "Printing was paused by the user", 145 | # 17: "Pause of front cover falling", 146 | # 18: "Calibrating Micro Lidar", 147 | # 19: "Calibrating Extrusion Flow", 148 | # 20: "Paused due to nozzle temperature malfunction", 149 | # 21: "Paused due to heat bed temperature malfunction" 150 | # 255: A1 mini uses this, most likely as idle 151 | # } 152 | -------------------------------------------------------------------------------- /firmware/conf.d/button.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | binary_sensor: 3 | - platform: gpio 4 | name: "Button" 5 | id: physical_button 6 | state_topic: 7 | internal: true 8 | pin: 9 | number: 0 10 | mode: INPUT_PULLUP 11 | inverted: true 12 | on_press: 13 | then: 14 | - script.execute: 15 | id: set_led_red 16 | led_number: -1 17 | - globals.set: 18 | id: button_press_duration 19 | value: "0" 20 | - script.execute: start_timer 21 | on_release: 22 | then: 23 | - script.execute: 24 | id: set_led_white 25 | led_number: -1 26 | - script.stop: timer_script 27 | - if: 28 | condition: 29 | lambda: "return id(button_press_duration) >= 10;" 30 | then: 31 | - logger.log: "Erasing WiFi credentials and restarting..." 32 | - button.press: factory_reset_openspool 33 | else: 34 | - logger.log: "Button released before 10 seconds, not performing factory reset." 35 | globals: 36 | - id: my_counter 37 | type: int 38 | initial_value: "0" 39 | - id: button_press_duration 40 | type: int 41 | initial_value: "0" 42 | script: 43 | - id: timer_script 44 | then: 45 | - while: 46 | condition: 47 | binary_sensor.is_on: physical_button 48 | then: 49 | - globals.set: 50 | id: button_press_duration 51 | value: !lambda "return id(button_press_duration) + 1;" 52 | - delay: 1s 53 | - id: start_timer 54 | then: 55 | - globals.set: 56 | id: button_press_duration 57 | value: "0" 58 | - script.execute: timer_script 59 | button: 60 | - platform: factory_reset 61 | name: "Factory Reset" 62 | id: factory_reset_openspool 63 | state_topic: 64 | internal: true 65 | -------------------------------------------------------------------------------- /firmware/conf.d/debug.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # NOTE None of these metrics are available until device is connected to MQTT 3 | # This feels like a bug, but not sure why 4 | debug: 5 | update_interval: 5s 6 | 7 | text_sensor: 8 | # Note that this only returns the esphome compiled version 9 | # It does not return esphome.platform.version, as that value is only available through the api 10 | # - platform: version 11 | # name: "Current Firmware Version" 12 | # hide_timestamp: true 13 | # state_topic: 14 | # # web_server: 15 | # # sorting_group_id: sorting_group_info 16 | - platform: template 17 | name: "OpenSpool Version" 18 | id: openspool_version 19 | state_topic: 20 | lambda: |- 21 | return to_string(ESPHOME_PROJECT_VERSION); 22 | icon: mdi:new-box 23 | web_server: 24 | sorting_group_id: sorting_group_info 25 | - platform: debug 26 | reset_reason: 27 | name: "Reset Reason" 28 | id: reset_reason 29 | state_topic: 30 | sensor: 31 | - platform: debug 32 | free: 33 | name: "Heap Free" 34 | id: heap_free 35 | state_topic: 36 | block: 37 | name: "Heap Max Block" 38 | id: heap_max_block 39 | state_topic: 40 | # loop_time: 41 | # name: "Loop Time" 42 | # id: loop_time 43 | # state_topic: 44 | -------------------------------------------------------------------------------- /firmware/conf.d/extra.yaml: -------------------------------------------------------------------------------- 1 | text_sensor: 2 | - platform: template 3 | name: "OpenSpool URL" 4 | id: openspool_url 5 | state_topic: 6 | icon: mdi:web 7 | web_server: 8 | sorting_group_id: sorting_group_extra 9 | lambda: |- 10 | std::string mac = id(mac_address).state; 11 | // Extract just the last 6 characters (3 pairs of hex digits) 12 | std::string last_6 = mac.substr(9, 2) + mac.substr(12, 2) + mac.substr(15, 2); 13 | return {"http://openspool-" + last_6 + ".local"}; 14 | - platform: template 15 | name: "BambuHandy URL" 16 | id: bambuhandy_url 17 | state_topic: 18 | icon: mdi:cellphone 19 | web_server: 20 | sorting_group_id: sorting_group_extra 21 | lambda: |- 22 | return {"bambulab://"}; 23 | 24 | button: 25 | - platform: template 26 | name: "OpenSpool Create Tag" 27 | id: write_url_button 28 | state_topic: 29 | icon: mdi:web 30 | web_server: 31 | sorting_group_id: sorting_group_extra 32 | on_press: 33 | then: 34 | # TODO: binary sensor that shows reader write_mode? 35 | # TODO: blink lights when light mode changes 36 | - logger.log: "Writing tag" #TODO: change log namespace 37 | - lambda: |- 38 | // This causes esp32 to lockup, stackoverflow perhaps? 39 | // Leaving this code here as an example of what not to do 40 | 41 | // std::string url = id(openspool_url).state; 42 | // nfc::NdefMessage message; 43 | // message.add_uri_record(url); 44 | //id(rfid_reader_spi_0).write_mode(&message); 45 | 46 | // This also causes it to lockup 47 | 48 | // std::string url = id(openspool_url).state; 49 | // auto message = std::make_unique(); 50 | // message->add_uri_record(url); 51 | // id(rfid_reader_spi_0).write_mode(message.get()); 52 | 53 | // deleting the message also causes a lockup 54 | 55 | // std::string url = id(openspool_url).state; 56 | // auto message = new nfc::NdefMessage(); 57 | // message->add_uri_record(url); 58 | // id(rfid_reader_spi_0).write_mode(message); 59 | // delete message; 60 | 61 | // even though it seems wrong to use 'new' without a 'delete' 62 | // this is the only code that doesn't crash. 63 | std::string url = id(openspool_url).state; 64 | ESP_LOGD("NFC", "Writing URL: %s", url.c_str()); 65 | auto message = new nfc::NdefMessage(); 66 | message->add_uri_record(url); 67 | id(rfid_reader_spi_0).write_mode(message); 68 | - wait_until: 69 | not: 70 | pn532.is_writing: 71 | id: rfid_reader_spi_0 72 | - logger.log: "Finished writing tag" #TODO: change log namespace 73 | - platform: template 74 | name: "BambuHandy Create Tag" 75 | id: write_bambuhandy_url_button 76 | state_topic: 77 | icon: mdi:cellphone 78 | web_server: 79 | sorting_group_id: sorting_group_extra 80 | on_press: 81 | then: 82 | # TODO: binary sensor that shows reader write_mode? 83 | # TODO: blink lights when light mode changes 84 | - logger.log: "Writing tag" #TODO: change log namespace 85 | - lambda: |- 86 | std::string url = id(bambuhandy_url).state; 87 | ESP_LOGD("NFC", "Writing URL: %s", url.c_str()); 88 | auto message = new nfc::NdefMessage(); 89 | message->add_uri_record(url); 90 | id(rfid_reader_spi_0).write_mode(message); 91 | - wait_until: 92 | not: 93 | pn532.is_writing: 94 | id: rfid_reader_spi_0 95 | - logger.log: "Finished writing tag" #TODO: change log namespace 96 | -------------------------------------------------------------------------------- /firmware/conf.d/i2c.yaml: -------------------------------------------------------------------------------- 1 | # i2c is very unreliable with the PN532 because it uses clock stretching 2 | # https://discord.com/channels/429907082951524364/429907082955718657/1303771768648896522 3 | # i2c: 4 | # - id: i2c_bus_a 5 | # sda: GPIO01 6 | # scl: GPIO02 7 | # scan: true 8 | # # frequency: 10kHz # https://github.com/esphome/issues/issues/4340 9 | # # frequency: 200kHz 10 | # # frequency: 40kHz 11 | # frequency: 100kHz # PN532 data sheet says it supports 100k and 400k 12 | # timeout: 2000µs # https://community.home-assistant.io/t/timed-out-waiting-for-readiness-from-pn532/683952 13 | # # timeout: 50ms 14 | # # [C][i2c.idf:078]: Frequency: 40000 Hz 15 | # # [C][i2c.idf:080]: Timeout: 13000us 16 | # pn532_i2c: 17 | # update_interval: 1s #TODO: Should this be more aggressive? 18 | # id: rfid_reader_i2c_1 19 | # address: 0x24 20 | # # i2c_id: i2c_bus_a 21 | # on_tag: 22 | # then: 23 | # - lambda: |- 24 | # ESP_LOGW("NFC", "I2C tag detected"); -------------------------------------------------------------------------------- /firmware/conf.d/improv-bluetooth.yaml: -------------------------------------------------------------------------------- 1 | esp32_improv: 2 | authorizer: none 3 | -------------------------------------------------------------------------------- /firmware/conf.d/improv-serial.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | improv_serial: # next_url: "http://{{ip_address}}" 3 | -------------------------------------------------------------------------------- /firmware/conf.d/led-external.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | substitutions: 3 | led_count: '9' 4 | light: 5 | - platform: esp32_rmt_led_strip 6 | name: LEDs 7 | id: neopixel_light 8 | state_topic: 9 | pin: ${led_pin} 10 | num_leds: ${led_count} 11 | rgb_order: GRB 12 | chipset: WS2812 13 | rmt_channel: 0 14 | default_transition_length: 0.4s 15 | restore_mode: RESTORE_DEFAULT_ON 16 | entity_category: diagnostic 17 | icon: mdi:led-strip 18 | effects: 19 | - addressable_rainbow: 20 | name: Rainbow 21 | speed: 25 22 | width: 15 23 | - addressable_lambda: 24 | name: Breathing Blue 25 | update_interval: 10ms 26 | lambda: |- 27 | static float b = 0; 28 | b = (sin(millis() / 500.0) + 1.0) / 2.0 * 0.6 + 0.4; 29 | auto color = esphome::light::ESPColor(0, 0, uint8_t(255 * b)); 30 | it.all() = color; 31 | - addressable_lambda: 32 | name: Apple Breathing 33 | update_interval: 10ms 34 | lambda: |- 35 | const float PI = 3.14159265359; 36 | //TODO: 42.546 should be replaced by 83.333 37 | float t = millis() / 2000.0; 38 | float brightness_float = (exp(sin(t * PI)) - 0.368) * 42.546; 39 | uint8_t brightness = uint8_t(brightness_float); 40 | auto color = esphome::light::ESPColor(brightness, 0, 0); 41 | for (int i = 0; i < it.size(); ++i) { 42 | it[i] = color; 43 | } 44 | # - strobe: 45 | # name: "Data Upload" 46 | # colors: 47 | # - state: TRUE 48 | # brightness: 100% 49 | # duration: 50ms 50 | # red: 0% 51 | # green: 100% 52 | # blue: 0% 53 | # - state: FALSE 54 | # duration: 50ms 55 | script: 56 | - id: set_led_red 57 | parameters: 58 | led_number: int8_t 59 | then: 60 | - light.addressable_set: 61 | id: neopixel_light 62 | color_brightness: 100% 63 | range_from: !lambda "return led_number < 0 ? 0 : led_number;" 64 | range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" 65 | red: 100% 66 | green: 0% 67 | blue: 0% 68 | - id: set_led_green 69 | parameters: 70 | led_number: int8_t 71 | then: 72 | - light.addressable_set: 73 | id: neopixel_light 74 | color_brightness: 100% 75 | range_from: !lambda "return led_number < 0 ? 0 : led_number;" 76 | range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" 77 | red: 0% 78 | green: 100% 79 | blue: 0% 80 | - id: set_led_blue 81 | parameters: 82 | led_number: int8_t 83 | then: 84 | - light.addressable_set: 85 | id: neopixel_light 86 | color_brightness: 100% 87 | range_from: !lambda "return led_number < 0 ? 0 : led_number;" 88 | range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" 89 | red: 0% 90 | green: 0% 91 | blue: 100% 92 | - id: set_led_yellow 93 | parameters: 94 | led_number: int8_t 95 | then: 96 | - light.addressable_set: 97 | id: neopixel_light 98 | color_brightness: 100% 99 | range_from: !lambda "return led_number < 0 ? 0 : led_number;" 100 | range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" 101 | red: 100% 102 | green: 100% 103 | blue: 0% 104 | - id: set_led_white 105 | parameters: 106 | led_number: int8_t 107 | then: 108 | - light.addressable_set: 109 | id: neopixel_light 110 | color_brightness: 50% 111 | range_from: !lambda "return led_number < 0 ? 0 : led_number;" 112 | range_to: !lambda "return led_number < 0 ? ${led_count} : led_number;" 113 | red: 100% 114 | green: 100% 115 | blue: 100% 116 | - id: set_all_leds_white 117 | then: 118 | - light.turn_on: 119 | id: neopixel_light 120 | effect: none 121 | brightness: 50% 122 | red: 100% 123 | green: 100% 124 | blue: 100% 125 | - id: set_led_off 126 | then: 127 | - light.turn_on: 128 | id: neopixel_light 129 | brightness: 0% 130 | red: 0% 131 | green: 0% 132 | blue: 0% 133 | effect: none 134 | - delay: 50ms 135 | - light.turn_off: 136 | id: neopixel_light 137 | transition_length: 0s 138 | - id: set_led_rainbow 139 | then: 140 | - light.turn_on: 141 | id: neopixel_light 142 | effect: Rainbow 143 | - id: set_led_breathing_blue 144 | then: 145 | - light.turn_on: 146 | id: neopixel_light 147 | effect: none 148 | - light.turn_on: 149 | id: neopixel_light 150 | effect: Breathing Blue 151 | brightness: 100% 152 | - id: set_led_breathing_green 153 | then: 154 | - light.turn_on: 155 | id: neopixel_light 156 | effect: Breathing Green 157 | brightness: 100% 158 | - id: set_led_apple_breathing 159 | then: 160 | - light.turn_on: 161 | id: neopixel_light 162 | effect: Apple Breathing 163 | brightness: 100% 164 | -------------------------------------------------------------------------------- /firmware/conf.d/led-internal.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | light: 3 | - platform: esp32_rmt_led_strip 4 | name: "Internal LED" 5 | id: internal_led 6 | state_topic: 7 | pin: ${neopixel_pin} 8 | chipset: WS2812 9 | rmt_channel: 1 10 | rgb_order: GRB 11 | num_leds: 1 12 | entity_category: diagnostic 13 | restore_mode: ALWAYS_OFF 14 | icon: mdi:led-strip 15 | -------------------------------------------------------------------------------- /firmware/conf.d/logger.yaml: -------------------------------------------------------------------------------- 1 | logger: 2 | level: VERBOSE 3 | logs: 4 | sensor: INFO 5 | pn532: DEBUG 6 | pn532_spi: DEBUG 7 | spi: INFO 8 | nfc: DEBUG 9 | i2c: INFO 10 | mqtt: DEBUG 11 | mqtt.component: DEBUG 12 | mqtt.client: DEBUG 13 | wifi: INFO 14 | wifi_esp32: INFO 15 | esp-tls: DEBUG 16 | esp-tls-mbedtls: DEBUG 17 | http_client: INFO 18 | http_request: INFO 19 | scheduler: INFO 20 | text_sensor: INFO 21 | text_sensor.filter: INFO 22 | esp32_rmt_led_strip: INFO 23 | debug: INFO 24 | web_server: DEBUG 25 | light: INFO 26 | light.addressable: INFO 27 | api: INFO 28 | improv_serial: VERBOSE 29 | json: INFO 30 | esp32.preferences: INFO 31 | esp-idf: DEBUG 32 | component: VERBOSE 33 | binary_sensor: INFO 34 | template.text: DEBUG 35 | text: DEBUG 36 | spi_device: ERROR 37 | nfc.ndef_message: DEBUG 38 | select: INFO 39 | number: INFO 40 | pn532.mifare_ultralight: VERBOSE 41 | pn532.mifare_classic: VERBOSE 42 | TAG: VERBOSE 43 | NFC: VERBOSE 44 | "": ERROR 45 | -------------------------------------------------------------------------------- /firmware/conf.d/mqtt_bambu_lan.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | substitutions: 3 | base_topic: "device/%s/report" #TODO: likely not used 4 | mqtt: 5 | id: bambu_mqtt 6 | broker: "" # Broker is set by text.bambu_ip_address 7 | port: 8883 8 | username: bblp 9 | password: "" # Password is set by text.bambu_lan_access_code 10 | client_id: ${name} 11 | discover_ip: false 12 | discovery: false 13 | discovery_retain: false 14 | discovery_prefix: 15 | use_abbreviations: false 16 | topic_prefix: 17 | log_topic: 18 | birth_message: 19 | topic: # Dont post update to MQTT 20 | payload: # Dont post update to MQTT 21 | will_message: 22 | topic: # Dont post update to MQTT 23 | payload: 24 | # Dont post update to MQTT 25 | # keepalive: 15s 26 | # idf_send_async: true 27 | skip_cert_cn_check: true 28 | certificate_authority: | 29 | -----BEGIN CERTIFICATE----- 30 | MIIDZTCCAk2gAwIBAgIUV1FckwXElyek1onFnQ9kL7Bk4N8wDQYJKoZIhvcNAQEL 31 | BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u 32 | LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yMjA0MDQwMzQyMTFaFw0zMjA0MDEw 33 | MzQyMTFaMEIxCzAJBgNVBAYTAkNOMSIwIAYDVQQKDBlCQkwgVGVjaG5vbG9naWVz 34 | IENvLiwgTHRkMQ8wDQYDVQQDDAZCQkwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB 35 | DwAwggEKAoIBAQDL3pnDdxGOk5Z6vugiT4dpM0ju+3Xatxz09UY7mbj4tkIdby4H 36 | oeEdiYSZjc5LJngJuCHwtEbBJt1BriRdSVrF6M9D2UaBDyamEo0dxwSaVxZiDVWC 37 | eeCPdELpFZdEhSNTaT4O7zgvcnFsfHMa/0vMAkvE7i0qp3mjEzYLfz60axcDoJLk 38 | p7n6xKXI+cJbA4IlToFjpSldPmC+ynOo7YAOsXt7AYKY6Glz0BwUVzSJxU+/+VFy 39 | /QrmYGNwlrQtdREHeRi0SNK32x1+bOndfJP0sojuIrDjKsdCLye5CSZIvqnbowwW 40 | 1jRwZgTBR29Zp2nzCoxJYcU9TSQp/4KZuWNVAgMBAAGjUzBRMB0GA1UdDgQWBBSP 41 | NEJo3GdOj8QinsV8SeWr3US+HjAfBgNVHSMEGDAWgBSPNEJo3GdOj8QinsV8SeWr 42 | 3US+HjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQABlBIT5ZeG 43 | fgcK1LOh1CN9sTzxMCLbtTPFF1NGGA13mApu6j1h5YELbSKcUqfXzMnVeAb06Htu 44 | 3CoCoe+wj7LONTFO++vBm2/if6Jt/DUw1CAEcNyqeh6ES0NX8LJRVSe0qdTxPJuA 45 | BdOoo96iX89rRPoxeed1cpq5hZwbeka3+CJGV76itWp35Up5rmmUqrlyQOr/Wax6 46 | itosIzG0MfhgUzU51A2P/hSnD3NDMXv+wUY/AvqgIL7u7fbDKnku1GzEKIkfH8hm 47 | Rs6d8SCU89xyrwzQ0PR853irHas3WrHVqab3P+qNwR0YirL0Qk7Xt/q3O1griNg2 48 | Blbjg3obpHo9 49 | -----END CERTIFICATE----- 50 | clean_session: true 51 | enable_on_boot: false 52 | on_connect: 53 | - logger.log: 54 | level: info 55 | format: "Connected to printer over MQTT!" 56 | on_disconnect: 57 | - logger.log: 58 | level: info 59 | format: "Disconnected from printer!" 60 | #TODO: enable status led on s2, set neopixel blue on s3 61 | 62 | # text_sensor: 63 | # - platform: mqtt_subscribe 64 | # name: "Bambu Printer Status" 65 | # id: bambu_printer_status 66 | # topic: "" # Set by text.bambu_serial_number 67 | # state_topic: 68 | # # internal: true 69 | # # web_server: 70 | # # sorting_group_id: sorting_group_printer_settings 71 | # filters: 72 | # - lambda: |- 73 | # DynamicJsonDocument doc(4096); 74 | # deserializeJson(doc, x); 75 | 76 | # if (doc["print"]["wifi_signal"]) { 77 | # return {}; 78 | # } 79 | # if (doc["print"]["command"] == "push_status") { 80 | # return {}; 81 | # } 82 | # if (doc["info"]["command"] == "get_version") { 83 | # return {}; 84 | # } 85 | # if (doc["system"]["command"] == "get_access_code") { 86 | # return {}; 87 | # } 88 | # if (doc["system"]["upgrade_state"]) { 89 | # return {}; 90 | # } 91 | # if (doc["print"]["ipcam"]) { 92 | # return {}; 93 | # } 94 | # return x; 95 | # # # on_value: 96 | # # # then: 97 | # # # - lambda: |- 98 | binary_sensor: 99 | # Note platform.status won't turn on if 'api' is enabled (yet disconnected) 100 | # api is required for dashboard_import, which is required for made-for-esphome program 101 | # api isn't actually used 102 | # - platform: status 103 | - platform: template 104 | name: "MQTT Connection" 105 | id: mqtt_connected 106 | state_topic: # Don't post update to MQTT 107 | icon: mdi:lan-connect 108 | web_server: 109 | sorting_group_id: sorting_group_printer_settings 110 | device_class: connectivity #TODO: use this class 111 | lambda: |- 112 | return id(bambu_mqtt)->is_connected(); 113 | 114 | # sensor: 115 | # - platform: mqtt_subscribe 116 | # name: "Data from topic" 117 | # id: mysensor 118 | # topic: testtopic/1234 119 | # state_topic: # Don't post update to MQTT 120 | -------------------------------------------------------------------------------- /firmware/conf.d/ota.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ota: 3 | - platform: esphome 4 | id: esphome_ota 5 | -------------------------------------------------------------------------------- /firmware/conf.d/psram-esp32s2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/esphome/issues/issues/4481#issuecomment-1556930070 3 | # https://github.com/esphome/issues/issues/5264#issuecomment-1869807913 4 | # - dio_qspi 5 | # - dio_opi 6 | # - qio_qspi 7 | # - qio_opi 8 | # - opi_qspi 9 | # - opi_opi 10 | 11 | # esphome: 12 | # platformio_options: 13 | # build_flags: 14 | # - -DBOARD_HAS_PSRAM 15 | # - -mfix-esp32-psram-cache-issue 16 | # OPI (8bits)> QIO (4bits) > DIO (2bits) 17 | # board_build.arduino.memory_type: qio_opi #NEEDED FOR PSRAM also dio_qspi/qsi_qspi for other configs 18 | # board_build.arduino.memory_type: dio_opi 19 | # board.build.arduino.memory_type: qio_qspi 20 | # board_build.memory_type: qio_opi 21 | # board.build.arduino.memory_type: qio_opi 22 | # board_build.memory_type: qio_dio 23 | # board.build.arduino.memory_type: qio_dio 24 | # board_build.memory_type: dio_dio # [wifi]: m f null 25 | # board.build.arduino.memory_type: dio_dio 26 | # board_build.memory_type: dio_qspi # [wifi]: m f null 27 | # board.build.arduino.memory_type: dio_qspi 28 | # board_build.arduino.memory_type: qio_qspi 29 | # Build flags are automaticlaly provided with the correct name 30 | # https://github.com/esphome/esphome/blob/dev/esphome/components/psram/__init__.py#L43-L50 31 | 32 | esp32: 33 | # https://community.home-assistant.io/t/esp32-s3-devkitc-1-n16r8-using-psram-howto/652601 34 | flash_size: 4MB #This should be auto detected, but users report having to define it to get psram working 35 | # Big warning about psram. 36 | # Many d1 mini s2 clones are rebranded and dont have the PSRAM enabled 37 | psram: 38 | mode: quad 39 | # speed: 40MHz 40 | speed: 80MHz 41 | 42 | # I (30) boot: ESP-IDF 5.3.1 2nd stage bootloader 43 | # I (30) boot: compile time Nov 10 2024 08:16:30 44 | # I (30) boot: chip revision: v0.0 45 | # I (30) boot.esp32s2: SPI Speed : 80MHz 46 | # I (30) boot.esp32s2: SPI Mode : DIO 47 | # I (30) boot.esp32s2: SPI Flash Size : 4MB 48 | 49 | sensor: 50 | - platform: debug 51 | psram: 52 | name: "Free PSRAM" 53 | id: free_psram 54 | state_topic: 55 | -------------------------------------------------------------------------------- /firmware/conf.d/psram-esp32s3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | psram: 3 | sensor: 4 | - platform: debug 5 | psram: 6 | name: "Free PSRAM" 7 | id: free_psram 8 | state_topic: 9 | -------------------------------------------------------------------------------- /firmware/conf.d/status_led.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | output: 3 | - id: onboard_led 4 | platform: gpio 5 | pin: 15 6 | inverted: false 7 | -------------------------------------------------------------------------------- /firmware/conf.d/time.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | time: 3 | - platform: sntp 4 | id: ntp_time 5 | servers: 6 | - "time.nist.gov" 7 | - "pool.ntp.org" 8 | timezone: "America/Denver" # Change this to your timezone 9 | on_time: 10 | - seconds: /10 11 | then: 12 | - component.update: time_sensor 13 | # Sensor to hold formatted time string 14 | text_sensor: 15 | - platform: template 16 | name: "Current Time" 17 | id: time_sensor 18 | state_topic: 19 | icon: "mdi:clock" 20 | update_interval: 10s 21 | lambda: |- 22 | auto time = id(ntp_time).now(); 23 | char str[20]; 24 | sprintf(str, "%02d:%02d:%02d", time.hour, time.minute, time.second); 25 | return {str}; 26 | 27 | # # Sensors for individual time components 28 | # sensor: 29 | # - platform: template 30 | # name: "Hour" 31 | # lambda: 'return id(ntp_time).now().hour;' 32 | # update_interval: 60s 33 | # icon: "mdi:clock-time-three-outline" 34 | # - platform: template 35 | # name: "Minute" 36 | # lambda: 'return id(ntp_time).now().minute;' 37 | # update_interval: 60s 38 | # icon: "mdi:clock-time-three-outline" 39 | # - platform: template 40 | # name: "Second" 41 | # lambda: 'return id(ntp_time).now().second;' 42 | # update_interval: 1s 43 | # icon: "mdi:clock-time-three-outline" 44 | -------------------------------------------------------------------------------- /firmware/conf.d/update.yaml: -------------------------------------------------------------------------------- 1 | # Esphome provides mulitple forms of OTA updates 2 | # The esphome-update allows you to update from a centralized esphome server (commonly installed on home assitant) 3 | # The http-update allows you to pull updates from github or other http server. 4 | # The http-update requires much more memory. In order to use http update, the problem of the missing 5 | # PRAM needs to be solved first. It might not be possible on Wemos D1Minis2 boards 6 | 7 | # update: 8 | # - platform: http_request 9 | # id: firmware_update 10 | # name: Firmware Update 11 | # source: https://raw.githubusercontent.com/spuder/openspool/main/firmware/manifest.json 12 | # update_interval: 60s #TODO: Change to 6h 13 | # web_server: 14 | # sorting_group_id: sorting_group_firmware 15 | 16 | # http_request: 17 | # ota: 18 | # - platform: http_request 19 | # id: my_ota 20 | # on_error: 21 | # then: 22 | # - logger.log: 23 | # format: "OTA update error %d" 24 | # args: ["x"] 25 | 26 | # Wemos D1Mini has 2MB PSRam which is needed for TLS/SSL 27 | # Note that real ESP32-S2 is part ESP32-S2FN4R2 28 | # There are counterfit boards which use ESP32-S2FH4 which don't have PS-RAM 29 | # psram: 30 | 31 | # button: 32 | # - platform: template 33 | # name: "Check for Updates" 34 | # update_id: firmware_update 35 | # on_press: 36 | # - update.is_available: 37 | # id: firmware_update 38 | 39 | # button: 40 | # - platform: template 41 | # name: "Update Firmware" 42 | # on_press: 43 | # - ota.http_request.flash: 44 | # url: "https://raw.githubusercontent.com/spuder/OpenSpool/main/firmware/openspool-esp32s2.ota.bin" #TODO: make board agnostic 45 | # md5_url: "https://raw.githubusercontent.com/spuder/OpenSpool/main/firmware/manifest.json" 46 | 47 | # text_sensor: 48 | # - platform: version 49 | # name: "Current Firmware Version" 50 | # id: current_version 51 | 52 | # - platform: http_request 53 | # name: "Latest Firmware Version" 54 | # url: https://raw.githubusercontent.com/spuder/OpenSpool/main/firmware/manifest.json 55 | # id: latest_version 56 | # update_interval: 1h 57 | # json_parse: true 58 | # json_path: "$.version" 59 | 60 | # text: 61 | # - platform: template 62 | # name: "Firmware Versions" 63 | # id: firmware_versions 64 | # lambda: |- 65 | # return "Current: " + id(current_version).state + 66 | # " | Latest: " + id(latest_version).state; 67 | -------------------------------------------------------------------------------- /firmware/conf.d/uptime.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | sensor: 3 | - platform: uptime 4 | name: Uptime 5 | id: openspool_uptime 6 | state_topic: 7 | -------------------------------------------------------------------------------- /firmware/conf.d/version.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Do not modify, github actions will update the number on each new release 3 | substitutions: 4 | version: "1.20.0" 5 | -------------------------------------------------------------------------------- /firmware/conf.d/web_server.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | captive_portal: 3 | web_server: 4 | id: openspool_web_server 5 | port: 80 6 | version: 3 7 | local: true 8 | sorting_groups: 9 | - id: sorting_group_filament_settings 10 | name: "Filament Settings" 11 | sorting_weight: -50 12 | - id: sorting_group_rfid 13 | name: "RFID Programming" 14 | sorting_weight: -40 15 | - id: sorting_group_printer_settings 16 | name: "Printer Settings" 17 | sorting_weight: -30 18 | - id: sorting_group_info 19 | name: "Information" 20 | sorting_weight: -20 21 | - id: sorting_group_extra 22 | name: "Extra" 23 | sorting_weight: -10 24 | -------------------------------------------------------------------------------- /firmware/conf.d/wifi.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | wifi: 3 | ap: 4 | ssid: "OpenSpool" 5 | reboot_timeout: 0s # This must stay at 0s if api and mqtt are enabled, otherwise it will reboot every 15 minutes 6 | #TODO: if it is in ap mode, then pulse blue 7 | # on_connect: 8 | # then: 9 | # - script.stop: set_led_rainbow 10 | # - script.execute: 11 | # id: set_led_white 12 | # led_number: -1 13 | # - script.stop: set_led_white 14 | # sensor: 15 | # - platform: wifi_signal 16 | # name: "WiFi Signal Sensor" 17 | # id: openspool_wifi_signal 18 | # state_topic: 19 | # update_interval: 60s 20 | # icon: mdi:wifi-strength-outline 21 | text_sensor: 22 | - platform: wifi_info 23 | mac_address: 24 | name: Mac Address 25 | id: mac_address 26 | state_topic: 27 | icon: mdi:network 28 | # - platform: wifi_info 29 | # ip_address: 30 | # name: IP Address 31 | # id: openspool_ip_address 32 | # state_topic: 33 | # icon: mdi:lan 34 | # ssid: 35 | # name: Wifi Network 36 | # id: openspool_ssid 37 | # state_topic: 38 | # icon: mdi:wifi 39 | -------------------------------------------------------------------------------- /firmware/esp32-s3-devkitc-1.yaml: -------------------------------------------------------------------------------- 1 | esp32: 2 | board: esp32-s3-devkitc-1 3 | 4 | substitutions: 5 | hide_ams_sensors: 'false' 6 | led_pin: GPIO21 7 | neopixel_pin: '48' #some boards have this on pin 38 8 | spi2_type: SPI2 9 | spi2_clk_pin: GPIO12 10 | spi2_miso_pin: GPIO13 11 | spi2_mosi_pin: GPIO11 12 | 13 | rfid0_spi_interface: SPI2 14 | rfid0_ss_pin: GPIO04 15 | 16 | rfid1_spi_interface: SPI2 17 | rfid1_ss_pin: GPIO05 18 | 19 | rfid2_spi_interface: SPI2 20 | rfid2_ss_pin: GPIO06 21 | 22 | rfid3_spi_interface: SPI2 23 | rfid3_ss_pin: GPIO07 24 | 25 | # rfid4_spi_interface: SPI2 26 | # rfid4_ss_pin: GPIO05 27 | 28 | # rfid5_spi_interface: SPI2 29 | # rfid5_ss_pin: GPIO04 30 | 31 | # spi3_type: SPI3 32 | # spi3_clk_pin: GPIO18 33 | # spi3_miso_pin: GPIO17 34 | # spi3_mosi_pin: GPIO16 35 | 36 | # rfid6_spi_interface: SPI3 37 | # rfid6_ss_pin: GPIO15 38 | 39 | # rfid7_spi_interface: SPI3 40 | # rfid7_ss_pin: GPIO9 41 | 42 | # rfid8_spi_interface: SPI3 43 | # rfid8_ss_pin: GPI14 # TODO: Verify this is compatible 44 | 45 | packages: 46 | openspool-ams: !include openspool-ams.yaml 47 | improv-serial: !include conf.d/improv-serial.yaml 48 | #improv-bluetooth: !include conf.d/improv-bluetooth.yaml 49 | #led-internal: !include conf.d/led-internal.yaml 50 | extra: !include conf.d/extra.yaml 51 | 52 | dashboard_import: 53 | package_import_url: github://spuder/openspool/firmware/esp32-s3-devkitc-1.yaml@main 54 | import_full_config: false -------------------------------------------------------------------------------- /firmware/lolin_s2_mini.yaml: -------------------------------------------------------------------------------- 1 | esp32: 2 | board: lolin_s2_mini 3 | 4 | substitutions: 5 | hide_ams_sensors: 'true' 6 | led_pin: GPIO16 7 | spi2_type: SPI2 #SPI2,SPI3,any,hardware 8 | spi2_clk_pin: GPIO36 9 | spi2_miso_pin: GPIO37 10 | spi2_mosi_pin: GPIO35 11 | rfid0_spi_interface: SPI2 12 | rfid0_ss_pin: GPIO34 13 | #rfid1_spi_interface: SPI2 14 | #rfid1_ss_pin: GPIO21 15 | ## rfid1_spi_interface: SPI2 16 | ## rfid1_ss_pin: GPIO08 17 | 18 | # D1 Mini S2 Boards will reboot when doing tasks like writing to PN532 unless wifi power is reduced 19 | wifi: 20 | output_power: 10 # https://github.com/esphome/issues/issues/3988#issuecomment-1449954758 21 | 22 | packages: 23 | openspool-mini: !include openspool-mini.yaml 24 | #improv-serial: !include conf.d/improv-serial.yaml 25 | #extra: !include conf.d/extra.yaml 26 | button: !include conf.d/button.yaml 27 | 28 | dashboard_import: 29 | package_import_url: github://spuder/openspool/firmware/lolin_s2_mini.yaml@main 30 | import_full_config: false -------------------------------------------------------------------------------- /firmware/lolin_s3_mini.yaml: -------------------------------------------------------------------------------- 1 | esp32: 2 | board: lolin_s3_mini 3 | variant: esp32s3 #TOOD: add support upstream for lolin_s3_mini 4 | 5 | substitutions: 6 | hide_ams_sensors: 'true' 7 | led_pin: GPIO16 8 | spi2_type: SPI2 #SPI2,SPI3,any,hardware #TODO: should this be hardware? 9 | spi2_clk_pin: GPIO38 10 | spi2_miso_pin: GPIO44 11 | spi2_mosi_pin: GPIO36 12 | rfid0_spi_interface: SPI2 13 | rfid0_ss_pin: GPIO34 14 | neopixel_pin: '47' 15 | 16 | packages: 17 | openspool-mini: !include openspool-mini.yaml 18 | #led-internal: !include conf.d/led-internal.yaml 19 | #improv-bluetooth: !include conf.d/improv-bluetooth.yaml 20 | extra: !include conf.d/extra.yaml 21 | button: !include conf.d/button.yaml 22 | 23 | dashboard_import: 24 | package_import_url: github://spuder/openspool/firmware/lolin_s3_mini.yaml@main 25 | import_full_config: false -------------------------------------------------------------------------------- /firmware/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OpenSpool", 3 | "version": "0.0.1", 4 | "builds": [ 5 | { 6 | "chipFamily": "ESP32-S2", 7 | "ota": { 8 | "path": "openspool-esp32s2.ota.bin", 9 | "md5": "44ec4ebf8e742cc8e6d0527e65b0aa82", 10 | "summary": "Add manifest.json", 11 | "release_url": "https://github.com/spuder/openspool/releases/download/1.4.6" 12 | }, 13 | "parts": [ 14 | { 15 | "path": "https://github.com/spuder/openspool/releases/download/1.4.6/openspool-esp32s2.factory.bin", 16 | "offset": 0 17 | } 18 | ] 19 | } 20 | ], 21 | "new_install_prompt_erase": true 22 | } -------------------------------------------------------------------------------- /firmware/openspool-ams.yaml: -------------------------------------------------------------------------------- 1 | 2 | packages: 3 | base: !include common.yaml 4 | psram: !include conf.d/psram-esp32s3.yaml 5 | pn_532_rfid-ams: !include conf.d/pn532_rfid-ams.yaml -------------------------------------------------------------------------------- /firmware/openspool-mini.yaml: -------------------------------------------------------------------------------- 1 | 2 | packages: 3 | base: !include common.yaml -------------------------------------------------------------------------------- /hardware/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Fritzing Library Credit 4 | 5 | PN532: https://domoticx.com/module-pn532-rfid-nfc-transciever/ 6 | 7 | Wemos D1 Mini S2: https://forum.fritzing.org/t/esp32-s2-mini-part/18737/2 -------------------------------------------------------------------------------- /hardware/fritzing components/ESP32-S3-WROOM-1-N16R8-dev-board.fzpz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/fritzing components/ESP32-S3-WROOM-1-N16R8-dev-board.fzpz -------------------------------------------------------------------------------- /hardware/fritzing components/PN532 Elechouse RFID NFC Module V3.fzpz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/fritzing components/PN532 Elechouse RFID NFC Module V3.fzpz -------------------------------------------------------------------------------- /hardware/fritzing components/WS2812 RGB LED strip.fzpz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/fritzing components/WS2812 RGB LED strip.fzpz -------------------------------------------------------------------------------- /hardware/fritzing components/Wemos-s2-mini.fzpz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/fritzing components/Wemos-s2-mini.fzpz -------------------------------------------------------------------------------- /hardware/fritzing components/wago-221-413.fzpz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/fritzing components/wago-221-413.fzpz -------------------------------------------------------------------------------- /hardware/fritzing components/wago-221-415.fzpz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/fritzing components/wago-221-415.fzpz -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1-color/Gerber_PCB_PCB_OpenSpool-Mini-Daughterboard_2025-01-10_2025-01-10.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1-color/Gerber_PCB_PCB_OpenSpool-Mini-Daughterboard_2025-01-10_2025-01-10.zip -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1-color/README.md: -------------------------------------------------------------------------------- 1 | # V1 has a defect 2 | 3 | The 3rd led is shorted because the ground pad got mistakenly filled with the signal wire 4 | 5 | 6 | ![](../v1/IMG_6507%20Large.jpeg) -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1-color/Screenshot 2025-01-10 at 20.30.41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1-color/Screenshot 2025-01-10 at 20.30.41.png -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1-color/Screenshot 2025-01-10 at 20.30.48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1-color/Screenshot 2025-01-10 at 20.30.48.png -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/BOM_OpenSpool-Mini-Daughterboard_2025-01-10.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1/BOM_OpenSpool-Mini-Daughterboard_2025-01-10.csv -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/EasyEDA.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1/EasyEDA.zip -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/Gerber_OpenSpool-Mini-Daughterboard_PCB_OpenSpool-Mini-Daughterboard_2025-01-10.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1/Gerber_OpenSpool-Mini-Daughterboard_PCB_OpenSpool-Mini-Daughterboard_2025-01-10.zip -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/IMG_6507 Large.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1/IMG_6507 Large.jpeg -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/PickAndPlace_PCB_OpenSpool-Mini-Daughterboard_2025-01-10.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1/PickAndPlace_PCB_OpenSpool-Mini-Daughterboard_2025-01-10.csv -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/README.md: -------------------------------------------------------------------------------- 1 | # V1 has a defect 2 | 3 | The 3rd led is shorted because the ground pad got mistakenly filled with the signal wire 4 | 5 | 6 | ![](./IMG_6507%20Large.jpeg) -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/Schematic_OpenSpool-Mini-Daughterboard_2025-01-10.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1/Schematic_OpenSpool-Mini-Daughterboard_2025-01-10.pdf -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/Screenshot 2025-01-10 at 18.37.22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1/Screenshot 2025-01-10 at 18.37.22.png -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/Screenshot 2025-01-10 at 18.37.32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1/Screenshot 2025-01-10 at 18.37.32.png -------------------------------------------------------------------------------- /hardware/openspool-mini-daughterboard/v1/Screenshot 2025-01-10 at 18.37.39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini-daughterboard/v1/Screenshot 2025-01-10 at 18.37.39.png -------------------------------------------------------------------------------- /hardware/openspool-mini/BOM.csv: -------------------------------------------------------------------------------- 1 | Part Name,Quantity,Description,Link 1,Link 2 2 | Wemos D1 Mini S3,1,ESP32-S3 Development Board,N/A,aliexpress.us/item/3256805262904443.html 3 | PN532 Small,1,NFC/RFID Reader Module Compact Version,amazon.com (AMZ: 4eoBz8s),aliexpress.us/item/3256805787598774.html 4 | NTAG 215/216,1+,13.56Mhz NFC Tags >500 bytes,amazon.com (AMZ: 4epJzpO),N/A 5 | WS2812B LED,1,Addressable RGB LED,amazon.com (AMZ: 40FFOt5),N/A 6 | 2.54mm Headers,1 set,Pin Headers for Connections,amazon.com (AMZ: 4en6138),N/A 7 | Logic Level Converter,1,3.3v to 5v Bi-directional Converter,amazon.com (AMZ: 3UMfMkp),N/A 8 | Jumper Wires F-F,8,Female to Female Jumper Cables,amazon.com (AMZ: 3AMwRDM),N/A -------------------------------------------------------------------------------- /hardware/openspool-mini/OpenSpool-Mini.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/OpenSpool-Mini.fzz -------------------------------------------------------------------------------- /hardware/openspool-mini/v1.0/Gerber_OpenSpool_PCB_OpenSpool_2024-11-05.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v1.0/Gerber_OpenSpool_PCB_OpenSpool_2024-11-05.zip -------------------------------------------------------------------------------- /hardware/openspool-mini/v2.0/Gerber_OpenSpool_PCB_OpenSpool_2024-11-19.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v2.0/Gerber_OpenSpool_PCB_OpenSpool_2024-11-19.zip -------------------------------------------------------------------------------- /hardware/openspool-mini/v2.0/Schematic_OpenSpool_2024-11-19.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v2.0/Schematic_OpenSpool_2024-11-19.pdf -------------------------------------------------------------------------------- /hardware/openspool-mini/v2.0/Screenshot 2024-11-19 at 22.33.28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v2.0/Screenshot 2024-11-19 at 22.33.28.png -------------------------------------------------------------------------------- /hardware/openspool-mini/v2.0/Screenshot 2024-11-20 at 10.53.56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v2.0/Screenshot 2024-11-20 at 10.53.56.png -------------------------------------------------------------------------------- /hardware/openspool-mini/v2.1/Gerber_OpenSpool_PCB_OpenSpool_2024-11-20.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v2.1/Gerber_OpenSpool_PCB_OpenSpool_2024-11-20.zip -------------------------------------------------------------------------------- /hardware/openspool-mini/v2.1/Screenshot 2024-11-20 at 10.53.56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v2.1/Screenshot 2024-11-20 at 10.53.56.png -------------------------------------------------------------------------------- /hardware/openspool-mini/v3.0/BOM_OpenSpool_2024-12-14.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v3.0/BOM_OpenSpool_2024-12-14.csv -------------------------------------------------------------------------------- /hardware/openspool-mini/v3.0/Gerber_OpenSpool_PCB_OpenSpool_2024-12-14.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v3.0/Gerber_OpenSpool_PCB_OpenSpool_2024-12-14.zip -------------------------------------------------------------------------------- /hardware/openspool-mini/v3.0/PickAndPlace_PCB_OpenSpool_2024-12-14.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v3.0/PickAndPlace_PCB_OpenSpool_2024-12-14.csv -------------------------------------------------------------------------------- /hardware/openspool-mini/v3.0/Schematic_OpenSpool_2024-12-14.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v3.0/Schematic_OpenSpool_2024-12-14.pdf -------------------------------------------------------------------------------- /hardware/openspool-mini/v3.0/Screenshot 2024-12-14 at 23.07.57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v3.0/Screenshot 2024-12-14 at 23.07.57.png -------------------------------------------------------------------------------- /hardware/openspool-mini/v3.0/Screenshot 2024-12-14 at 23.08.03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v3.0/Screenshot 2024-12-14 at 23.08.03.png -------------------------------------------------------------------------------- /hardware/openspool-mini/v3.1/BOM_OpenSpool_2024-12-24.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v3.1/BOM_OpenSpool_2024-12-24.csv -------------------------------------------------------------------------------- /hardware/openspool-mini/v3.1/Gerber_OpenSpool_PCB_OpenSpool_2024-12-25.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-mini/v3.1/Gerber_OpenSpool_PCB_OpenSpool_2024-12-25.zip -------------------------------------------------------------------------------- /hardware/openspool-pro/OpenSpool-AMS.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-pro/OpenSpool-AMS.fzz -------------------------------------------------------------------------------- /hardware/openspool-pro/v0.3/OpenSpool Pro - v0.3 - 250113.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-pro/v0.3/OpenSpool Pro - v0.3 - 250113.f3d -------------------------------------------------------------------------------- /hardware/openspool-pro/v0.3/OpenSpool Pro - v0.3 - 250113.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-pro/v0.3/OpenSpool Pro - v0.3 - 250113.pdf -------------------------------------------------------------------------------- /hardware/openspool-pro/v0.3/Screenshot_2025-01-13_at_10.35.16_PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/openspool-pro/v0.3/Screenshot_2025-01-13_at_10.35.16_PM.png -------------------------------------------------------------------------------- /hardware/pn532/PN532_ Manual_V3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/pn532/PN532_ Manual_V3.pdf -------------------------------------------------------------------------------- /hardware/pn532/PN532_AntennaDesign_v1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/pn532/PN532_AntennaDesign_v1.0.pdf -------------------------------------------------------------------------------- /hardware/pn532/PN532_C1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/pn532/PN532_C1.pdf -------------------------------------------------------------------------------- /hardware/pn532/PN532_shematic_drowing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/hardware/pn532/PN532_shematic_drowing.pdf -------------------------------------------------------------------------------- /images/BambuLogo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/BambuLogo1.png -------------------------------------------------------------------------------- /images/Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/Banner.png -------------------------------------------------------------------------------- /images/Change_fillament.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/Change_fillament.png -------------------------------------------------------------------------------- /images/Headers1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/Headers1.png -------------------------------------------------------------------------------- /images/Icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/Icon3.png -------------------------------------------------------------------------------- /images/LED1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/LED1.png -------------------------------------------------------------------------------- /images/LED2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/LED2.png -------------------------------------------------------------------------------- /images/LLC1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/LLC1.png -------------------------------------------------------------------------------- /images/Logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/Logo.xcf -------------------------------------------------------------------------------- /images/Logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/Logo2.png -------------------------------------------------------------------------------- /images/Logo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/Logo3.png -------------------------------------------------------------------------------- /images/Logo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/Logo4.png -------------------------------------------------------------------------------- /images/NFC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/NFC.png -------------------------------------------------------------------------------- /images/NFC2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/NFC2.png -------------------------------------------------------------------------------- /images/NFC3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/NFC3.png -------------------------------------------------------------------------------- /images/OpenSpoolLogo.ai.ps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/OpenSpoolLogo.ai.ps -------------------------------------------------------------------------------- /images/OpenSpoolLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/OpenSpoolLogo.png -------------------------------------------------------------------------------- /images/OpenSpoolLogo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/OpenSpoolLogo2.png -------------------------------------------------------------------------------- /images/OpenSpoolLogoMedium1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/OpenSpoolLogoMedium1.png -------------------------------------------------------------------------------- /images/OpenSpoolLogoSmall1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/OpenSpoolLogoSmall1.png -------------------------------------------------------------------------------- /images/OpenSpoolMiniWiringDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/OpenSpoolMiniWiringDiagram.png -------------------------------------------------------------------------------- /images/OpenSpoolMiniWiringDiagram2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/OpenSpoolMiniWiringDiagram2.png -------------------------------------------------------------------------------- /images/PCB1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/PCB1.png -------------------------------------------------------------------------------- /images/PN532-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/PN532-4.jpg -------------------------------------------------------------------------------- /images/PSRAM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/PSRAM.png -------------------------------------------------------------------------------- /images/PrinterSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/PrinterSettings.png -------------------------------------------------------------------------------- /images/README.md: -------------------------------------------------------------------------------- 1 | # OpenSpool Colors 2 | 3 | 4 | RGB = `255,0,132` 5 | Hex = `#ff0084` -------------------------------------------------------------------------------- /images/RainbowReel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/RainbowReel.png -------------------------------------------------------------------------------- /images/Simulator Screenshot - iPhone SE (3rd generation) - 2025-01-03 at 22.12.15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/Simulator Screenshot - iPhone SE (3rd generation) - 2025-01-03 at 22.12.15.png -------------------------------------------------------------------------------- /images/WebInterface1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/WebInterface1.png -------------------------------------------------------------------------------- /images/WiringDiagram1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/WiringDiagram1.png -------------------------------------------------------------------------------- /images/WiringDiagram2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/WiringDiagram2.png -------------------------------------------------------------------------------- /images/jumper1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/jumper1.png -------------------------------------------------------------------------------- /images/made-for-esphome-white-on-black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/mqttx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/mqttx.png -------------------------------------------------------------------------------- /images/nfc-bambuhandy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/nfc-bambuhandy.png -------------------------------------------------------------------------------- /images/nfc-openspool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/nfc-openspool.png -------------------------------------------------------------------------------- /images/openspool-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/openspool-transparent.png -------------------------------------------------------------------------------- /images/phone2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/phone2.png -------------------------------------------------------------------------------- /images/pn532-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/pn532-large.png -------------------------------------------------------------------------------- /images/pn532-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/pn532-small.png -------------------------------------------------------------------------------- /images/tindie/IMG_6108.HEIC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6108.HEIC -------------------------------------------------------------------------------- /images/tindie/IMG_6108.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6108.png -------------------------------------------------------------------------------- /images/tindie/IMG_6109.HEIC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6109.HEIC -------------------------------------------------------------------------------- /images/tindie/IMG_6113.HEIC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6113.HEIC -------------------------------------------------------------------------------- /images/tindie/IMG_6120.HEIC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6120.HEIC -------------------------------------------------------------------------------- /images/tindie/IMG_6120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6120.png -------------------------------------------------------------------------------- /images/tindie/IMG_6125-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6125-small.png -------------------------------------------------------------------------------- /images/tindie/IMG_6125.HEIC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6125.HEIC -------------------------------------------------------------------------------- /images/tindie/IMG_6125.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6125.jpg -------------------------------------------------------------------------------- /images/tindie/IMG_6125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/tindie/IMG_6125.png -------------------------------------------------------------------------------- /images/wemos-d1mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/wemos-d1mini.png -------------------------------------------------------------------------------- /images/wemos-d1mini2.png.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/wemos-d1mini2.png.jpg -------------------------------------------------------------------------------- /images/wemos-d1minis3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spuder/OpenSpool/34cb4f443189698f145434ba93f8b9c3c4989c3c/images/wemos-d1minis3.png -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Be sure to install git-semver 4 | # (git clone https://github.com/markchalloner/git-semver.git && sudo git-semver/install.sh) 5 | 6 | # Check if git-semver is installed 7 | if ! command -v git-semver &> /dev/null 8 | then 9 | echo "git-semver is not installed. Please install it first." 10 | exit 1 11 | fi 12 | 13 | echo "Select release type:" 14 | echo "1) Patch" 15 | echo "2) Minor" 16 | echo "3) Major" 17 | read -p "Enter your choice (1-3): " choice 18 | 19 | case $choice in 20 | 1) target="patch" ;; 21 | 2) target="minor" ;; 22 | 3) target="major" ;; 23 | *) echo "Invalid choice"; exit 1 ;; 24 | esac 25 | 26 | new_version=$(git-semver -target $target) 27 | echo "New version will be: $new_version" 28 | 29 | read -p "Proceed with tagging? (y/n): " confirm 30 | if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then 31 | git tag -a "v$new_version" -m "Release $new_version" 32 | git push origin "v$new_version" 33 | echo "Tagged and pushed v$new_version" 34 | else 35 | echo "Aborted" 36 | fi --------------------------------------------------------------------------------