├── .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 | Name
15 | Last Modified
16 | Size
17 |
18 | {% for file in files %}
19 |
20 | {{ file.name }}
21 | {{ file.modified_time | date: "%Y-%m-%d %H:%M" }}
22 |
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 |
31 |
32 | {% endfor %}
33 |
--------------------------------------------------------------------------------
/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 |
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 | 
25 |
26 | 
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 | 
10 |
11 |
12 |
13 |
14 |
🔩 Install the firmware
15 |
16 |
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 | 
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 | 
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 |
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 | 
--------------------------------------------------------------------------------
/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 | 
--------------------------------------------------------------------------------
/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 | 
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------