├── .github └── workflows │ ├── arduino-report.yml │ ├── arduino.yml │ ├── release.yml │ └── static.yml ├── .gitignore ├── 3DPrints ├── FOSSA ATM 3mm Lock Lever Shim.stl ├── FOSSA ATM 4mm Lock Lever Shim.stl ├── FOSSA ATM Back Box.stl ├── FOSSA ATM Backbox Note Holder.stl ├── FOSSA ATM Backbox.stl ├── FOSSA ATM Door.stl ├── FOSSA ATM Facia.stl ├── FOSSA ATM Front Facia.stl ├── FOSSA ATM LNBits Board Cover.stl ├── FOSSA ATM Screen Backplate.stl ├── FOSSA ATM Screen Mount Test.stl ├── FOSSA ATM Slot Cover.stl ├── FOSSA ATM WT32-SC01 Backplate.stl ├── README.md ├── WT32-SC01 Case.stl └── WT32-SC01 ESP32 with 3.5in touch screen.f3d ├── Cutting Template.pdf ├── INSTALLER.md ├── LICENSE ├── PCB ├── Fossa PCB-B_Cu.svg ├── Fossa PCB-Edge_Cuts.svg ├── Fossa PCB-F_Cu.svg ├── Fossa PCB-NPTH.drl ├── Fossa PCB-PTH.drl ├── Fossa PCB.kicad_pcb ├── Fossa PCB.kicad_prl ├── Fossa PCB.kicad_pro ├── Fossa PCB.kicad_sch ├── Fossa PCB.step └── fp-info-cache ├── PSDs ├── FOSSA - Primary components.psd ├── FOSSA - Primary wiring reference.psd ├── FOSSA - The FOSS ATM.psd └── README.md ├── README.md ├── build-webinstaller.sh ├── build.sh ├── config.js ├── debug.sh ├── docs ├── Cutting template.pdf ├── NV10 operations manual.pdf ├── NV10 user manual.pdf └── Wiring diagram.pdf ├── fossa-tests ├── README.md ├── testbills │ └── testbills.ino ├── testcoins │ └── testcoins.ino └── testprint │ └── testprint.ino ├── fossa ├── 100_config.ino ├── 101_translations.ino ├── 102_helpers.ino ├── 103_lnurl.ino ├── 104_printer.ino ├── 105_display.ino └── fossa.ino ├── img └── nv10-wiring.png ├── libraries └── QRCode │ ├── LICENSE.txt │ ├── README.md │ ├── examples │ └── QRCode │ │ └── QRCode.ino │ ├── generate_table.py │ ├── keywords.txt │ ├── library.properties │ ├── src │ ├── qrcoded.c │ └── qrcoded.h │ └── tests │ ├── BitBuffer.cpp │ ├── BitBuffer.hpp │ ├── QrCode.cpp │ ├── QrCode.hpp │ ├── QrSegment.cpp │ ├── QrSegment.hpp │ ├── README.md │ ├── run-tests.cpp │ └── run.sh ├── stls ├── WT32-SC01 enclosure lower.stl └── WT32-SC01 enclosure upper.stl ├── tft_config.txt ├── tft_config_IL9341.txt ├── tft_config_build_flags.sh └── versions.json /.github/workflows/arduino-report.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | - cron: '*/5 * * * *' 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: arduino/report-size-deltas@v1 9 | -------------------------------------------------------------------------------- /.github/workflows/arduino.yml: -------------------------------------------------------------------------------- 1 | name: arduino 2 | on: [ push, pull_request ] 3 | 4 | jobs: 5 | build-for-esp32: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | fqbn: 10 | - esp32:esp32:esp32 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: arduino/compile-sketches@v1 15 | with: 16 | enable-deltas-report: true 17 | github-token: ${{ secrets.GITHUB_TOKEN }} 18 | fqbn: ${{ matrix.fqbn }} 19 | platforms: | 20 | - name: esp32:esp32@2.0.17 21 | source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 22 | sketch-paths: | 23 | - fossa/fossa.ino 24 | libraries: | 25 | - name: ArduinoJson 26 | - name: uBitcoin 27 | - name: JC_Button 28 | - name: EspSoftwareSerial 29 | - name: Adafruit Thermal Printer Library 30 | - name: TFT_eSPI 31 | - source-path: ./libraries/QRCode 32 | 33 | # This step is needed to pass the size data to the report job. 34 | - name: Upload sketches report to workflow artifact 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: sketches-reports-esp32 38 | path: sketches-reports 39 | 40 | 41 | report: 42 | needs: build-for-esp32 43 | if: github.event_name == 'pull_request' 44 | runs-on: ubuntu-latest 45 | steps: 46 | # This step is needed to get the size data produced by the compile jobs 47 | - name: Download sketches reports artifact 48 | uses: actions/download-artifact@v4 49 | with: 50 | path: sketches-reports 51 | 52 | - uses: arduino/report-size-deltas@v1 53 | with: 54 | sketches-reports-source: sketches-reports 55 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+" 7 | 8 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 9 | permissions: 10 | contents: write 11 | pages: write 12 | id-token: write 13 | 14 | jobs: 15 | release: 16 | runs-on: ubuntu-latest 17 | steps: 18 | 19 | - uses: actions/checkout@v4 20 | with: 21 | ref: main 22 | 23 | - name: update version in repo 24 | env: 25 | tag: ${{ github.ref_name }} 26 | run: | 27 | tmp=$(mktemp) 28 | jq --arg version $tag '.versions |= [$version] + .' versions.json > "$tmp" && mv "$tmp" versions.json 29 | git config --global user.name 'Alan Bits' 30 | git config --global user.email 'alan@lnbits.com' 31 | git commit -am "[CHORE] update version to $tag" 32 | git push 33 | git push --delete origin $tag 34 | git tag -fa $tag -m "update via workflow" 35 | git push --tags 36 | 37 | - name: Install Arduino CLI 38 | uses: arduino/setup-arduino-cli@v1 39 | 40 | - name: build sketch with arduino cli 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | tag: ${{ github.ref_name }} 44 | run: | 45 | sh build.sh 46 | 47 | - name: Create github release 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | tag: ${{ github.ref_name }} 51 | run: | 52 | gh release create "$tag" --generate-notes ./build/fossa.ino.bootloader.bin \ 53 | ./build/fossa.ino.bin ./build/fossa.ino.partitions.bin 54 | 55 | deploy: 56 | needs: [ release ] 57 | uses: ./.github/workflows/static.yml 58 | -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Allows you to run this workflow from other workflows 13 | workflow_call: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 23 | concurrency: 24 | group: "pages" 25 | cancel-in-progress: false 26 | 27 | jobs: 28 | deploy: 29 | environment: 30 | name: github-pages 31 | url: ${{ steps.deployment.outputs.page_url }} 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | with: 37 | ref: main 38 | - name: Use Node.js 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: '20.x' 42 | - name: build webinstaller 43 | run: | 44 | sh build-webinstaller.sh 45 | cd hardware-installer 46 | npm install 47 | npx vite build 48 | - name: Setup Pages 49 | uses: actions/configure-pages@v3 50 | - name: Upload artifact 51 | uses: actions/upload-pages-artifact@v3 52 | with: 53 | path: "hardware-installer/dist" 54 | - name: Deploy to GitHub Pages 55 | id: deployment 56 | uses: actions/deploy-pages@v4 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | PCB/Fossa PCB-backups 2 | PCB/Gerbers 3 | build 4 | .venv 5 | .cache 6 | -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM 3mm Lock Lever Shim.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM 3mm Lock Lever Shim.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM 4mm Lock Lever Shim.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM 4mm Lock Lever Shim.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM Back Box.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM Back Box.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM Backbox Note Holder.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM Backbox Note Holder.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM Backbox.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM Backbox.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM Door.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM Door.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM Facia.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM Facia.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM Front Facia.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM Front Facia.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM LNBits Board Cover.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM LNBits Board Cover.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM Screen Backplate.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM Screen Backplate.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM Screen Mount Test.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM Screen Mount Test.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM Slot Cover.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM Slot Cover.stl -------------------------------------------------------------------------------- /3DPrints/FOSSA ATM WT32-SC01 Backplate.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/FOSSA ATM WT32-SC01 Backplate.stl -------------------------------------------------------------------------------- /3DPrints/README.md: -------------------------------------------------------------------------------- 1 | ##### Few people have used a case for the WT32, this does however add the security risk that people have access to the USB. A flush finish and not using a case is advised, but we thought we'd share anyway. 2 | -------------------------------------------------------------------------------- /3DPrints/WT32-SC01 Case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/WT32-SC01 Case.stl -------------------------------------------------------------------------------- /3DPrints/WT32-SC01 ESP32 with 3.5in touch screen.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/3DPrints/WT32-SC01 ESP32 with 3.5in touch screen.f3d -------------------------------------------------------------------------------- /Cutting Template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/Cutting Template.pdf -------------------------------------------------------------------------------- /INSTALLER.md: -------------------------------------------------------------------------------- 1 |

2 | FOSSA! Free and Open Source ATM 3 |

4 |

NOW SUPPORTS ONCHAIN AND LIQUID PAYOUTS!!! (powered by magical sub-atomic trustless swaps!)

5 | 6 | ## Free and open-source bitcoin point-of-sale 7 | 8 | 9 | Fossa includes: 10 | 11 | FOSSA is a popular cheap DIY bitcoin ATM with many forks. 12 |

13 | FOSSA is offline, and can be used with a bill acceptor, coin acceptor or both! FOSSA supports a thermal printer, and can pay out in bitcoin on lightning, onchain or liquid (using the magic of trustless swaps, no onchain or liquid hotwallet needed!!!) 14 |

15 | HUGE thanks to all contributors and users, especially BlackCoffee and Ben Weeks! 16 | 17 | Join our telegram group MakerBits 18 | -------------------------------------------------------------------------------- /PCB/Fossa PCB-Edge_Cuts.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | SVG Image created as Fossa PCB-Edge_Cuts.svg date 2024/07/04 11:47:15 11 | Image generated by PCBNEW 12 | 15 | 16 | 19 | 20 | 23 | 24 | 27 | 28 | 31 | 32 | 35 | 36 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /PCB/Fossa PCB-NPTH.drl: -------------------------------------------------------------------------------- 1 | M48 2 | ; DRILL file {KiCad 7.0.6-0} date 2024 June 24, Monday 16:23:36 3 | ; FORMAT={-:-/ absolute / inch / decimal} 4 | ; #@! TF.CreationDate,2024-06-24T16:23:36+01:00 5 | ; #@! TF.GenerationSoftware,Kicad,Pcbnew,7.0.6-0 6 | ; #@! TF.FileFunction,NonPlated,1,2,NPTH 7 | FMAT,2 8 | INCH 9 | % 10 | G90 11 | G05 12 | T0 13 | M30 14 | -------------------------------------------------------------------------------- /PCB/Fossa PCB-PTH.drl: -------------------------------------------------------------------------------- 1 | M48 2 | ; DRILL file {KiCad 7.0.6-0} date 2024 June 24, Monday 16:23:36 3 | ; FORMAT={-:-/ absolute / inch / decimal} 4 | ; #@! TF.CreationDate,2024-06-24T16:23:36+01:00 5 | ; #@! TF.GenerationSoftware,Kicad,Pcbnew,7.0.6-0 6 | ; #@! TF.FileFunction,Plated,1,2,PTH 7 | FMAT,2 8 | INCH 9 | ; #@! TA.AperFunction,Plated,PTH,ComponentDrill 10 | T1C0.0315 11 | ; #@! TA.AperFunction,Plated,PTH,ComponentDrill 12 | T2C0.0394 13 | ; #@! TA.AperFunction,Plated,PTH,ComponentDrill 14 | T3C0.0433 15 | % 16 | G90 17 | G05 18 | T1 19 | X4.997Y-5.1731 20 | X4.997Y-5.2519 21 | X5.0757Y-5.1731 22 | X5.0757Y-5.2519 23 | X5.1544Y-5.1731 24 | X5.1544Y-5.2519 25 | X5.2332Y-5.1731 26 | X5.2332Y-5.2519 27 | X5.3119Y-5.1731 28 | X5.3119Y-5.2519 29 | X5.3907Y-5.1731 30 | X5.3907Y-5.2519 31 | X5.4694Y-5.1731 32 | X5.4694Y-5.2519 33 | X5.5481Y-5.1731 34 | X5.5481Y-5.2519 35 | X5.6269Y-5.1731 36 | X5.6269Y-5.2519 37 | X5.7056Y-5.1731 38 | X5.7056Y-5.2519 39 | X5.7844Y-5.1731 40 | X5.7844Y-5.2519 41 | X5.8631Y-5.1731 42 | X5.8631Y-5.2519 43 | X5.9419Y-5.1731 44 | X5.9419Y-5.2519 45 | X6.0206Y-5.1731 46 | X6.0206Y-5.2519 47 | X6.0993Y-5.1731 48 | X6.0993Y-5.2519 49 | X6.1781Y-5.1731 50 | X6.1781Y-5.2519 51 | X6.2568Y-5.1731 52 | X6.2568Y-5.2519 53 | X6.3356Y-5.1731 54 | X6.3356Y-5.2519 55 | X6.4143Y-5.1731 56 | X6.4143Y-5.2519 57 | X6.493Y-5.1731 58 | X6.493Y-5.2519 59 | T2 60 | X3.3Y-3.85 61 | X3.3Y-3.95 62 | X3.3Y-4.05 63 | X3.3Y-4.15 64 | X3.3Y-4.25 65 | X3.3Y-4.35 66 | X3.3Y-4.45 67 | X3.3Y-4.55 68 | X3.4Y-3.85 69 | X3.4Y-3.95 70 | X3.4Y-4.05 71 | X3.4Y-4.15 72 | X3.4Y-4.25 73 | X3.4Y-4.35 74 | X3.4Y-4.45 75 | X3.4Y-4.55 76 | X5.4752Y-3.3571 77 | X5.4752Y-4.05 78 | X7.05Y-3.3571 79 | X7.05Y-4.05 80 | X8.04Y-3.56 81 | X8.04Y-3.66 82 | X8.04Y-3.76 83 | X8.04Y-3.86 84 | X8.04Y-3.96 85 | X8.05Y-4.45 86 | X8.05Y-4.55 87 | X8.05Y-4.65 88 | X8.05Y-4.75 89 | X8.05Y-4.85 90 | T3 91 | X5.501Y-6.19 92 | X5.845Y-6.19 93 | T0 94 | M30 95 | -------------------------------------------------------------------------------- /PCB/Fossa PCB.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 0, 4 | "active_layer_preset": "All Layers", 5 | "auto_track_width": true, 6 | "hidden_netclasses": [], 7 | "hidden_nets": [], 8 | "high_contrast_mode": 0, 9 | "net_color_mode": 1, 10 | "opacity": { 11 | "images": 0.6, 12 | "pads": 1.0, 13 | "tracks": 1.0, 14 | "vias": 1.0, 15 | "zones": 0.6 16 | }, 17 | "selection_filter": { 18 | "dimensions": true, 19 | "footprints": true, 20 | "graphics": true, 21 | "keepouts": true, 22 | "lockedItems": false, 23 | "otherItems": true, 24 | "pads": true, 25 | "text": true, 26 | "tracks": true, 27 | "vias": true, 28 | "zones": true 29 | }, 30 | "visible_items": [ 31 | 0, 32 | 1, 33 | 2, 34 | 3, 35 | 4, 36 | 5, 37 | 8, 38 | 9, 39 | 10, 40 | 11, 41 | 12, 42 | 13, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36, 64 | 39, 65 | 40 66 | ], 67 | "visible_layers": "fffffff_ffffffff", 68 | "zone_display_mode": 0 69 | }, 70 | "meta": { 71 | "filename": "Fossa PCB.kicad_prl", 72 | "version": 3 73 | }, 74 | "project": { 75 | "files": [] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /PCB/Fossa PCB.kicad_pro: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "3dviewports": [], 4 | "design_settings": { 5 | "defaults": { 6 | "board_outline_line_width": 0.09999999999999999, 7 | "copper_line_width": 0.19999999999999998, 8 | "copper_text_italic": false, 9 | "copper_text_size_h": 1.5, 10 | "copper_text_size_v": 1.5, 11 | "copper_text_thickness": 0.3, 12 | "copper_text_upright": false, 13 | "courtyard_line_width": 0.049999999999999996, 14 | "dimension_precision": 4, 15 | "dimension_units": 3, 16 | "dimensions": { 17 | "arrow_length": 1270000, 18 | "extension_offset": 500000, 19 | "keep_text_aligned": true, 20 | "suppress_zeroes": false, 21 | "text_position": 0, 22 | "units_format": 1 23 | }, 24 | "fab_line_width": 0.09999999999999999, 25 | "fab_text_italic": false, 26 | "fab_text_size_h": 1.0, 27 | "fab_text_size_v": 1.0, 28 | "fab_text_thickness": 0.15, 29 | "fab_text_upright": false, 30 | "other_line_width": 0.15, 31 | "other_text_italic": false, 32 | "other_text_size_h": 1.0, 33 | "other_text_size_v": 1.0, 34 | "other_text_thickness": 0.15, 35 | "other_text_upright": false, 36 | "pads": { 37 | "drill": 0.762, 38 | "height": 1.524, 39 | "width": 1.524 40 | }, 41 | "silk_line_width": 0.15, 42 | "silk_text_italic": false, 43 | "silk_text_size_h": 1.0, 44 | "silk_text_size_v": 1.0, 45 | "silk_text_thickness": 0.15, 46 | "silk_text_upright": false, 47 | "zones": { 48 | "min_clearance": 0.5 49 | } 50 | }, 51 | "diff_pair_dimensions": [], 52 | "drc_exclusions": [], 53 | "meta": { 54 | "version": 2 55 | }, 56 | "rule_severities": { 57 | "annular_width": "error", 58 | "clearance": "error", 59 | "connection_width": "warning", 60 | "copper_edge_clearance": "error", 61 | "copper_sliver": "warning", 62 | "courtyards_overlap": "error", 63 | "diff_pair_gap_out_of_range": "error", 64 | "diff_pair_uncoupled_length_too_long": "error", 65 | "drill_out_of_range": "error", 66 | "duplicate_footprints": "warning", 67 | "extra_footprint": "warning", 68 | "footprint": "error", 69 | "footprint_type_mismatch": "ignore", 70 | "hole_clearance": "error", 71 | "hole_near_hole": "error", 72 | "invalid_outline": "error", 73 | "isolated_copper": "warning", 74 | "item_on_disabled_layer": "error", 75 | "items_not_allowed": "error", 76 | "length_out_of_range": "error", 77 | "lib_footprint_issues": "warning", 78 | "lib_footprint_mismatch": "warning", 79 | "malformed_courtyard": "error", 80 | "microvia_drill_out_of_range": "error", 81 | "missing_courtyard": "ignore", 82 | "missing_footprint": "warning", 83 | "net_conflict": "warning", 84 | "npth_inside_courtyard": "ignore", 85 | "padstack": "warning", 86 | "pth_inside_courtyard": "ignore", 87 | "shorting_items": "error", 88 | "silk_edge_clearance": "warning", 89 | "silk_over_copper": "warning", 90 | "silk_overlap": "warning", 91 | "skew_out_of_range": "error", 92 | "solder_mask_bridge": "error", 93 | "starved_thermal": "error", 94 | "text_height": "warning", 95 | "text_thickness": "warning", 96 | "through_hole_pad_without_hole": "error", 97 | "too_many_vias": "error", 98 | "track_dangling": "warning", 99 | "track_width": "error", 100 | "tracks_crossing": "error", 101 | "unconnected_items": "error", 102 | "unresolved_variable": "error", 103 | "via_dangling": "warning", 104 | "zones_intersect": "error" 105 | }, 106 | "rules": { 107 | "max_error": 0.005, 108 | "min_clearance": 0.0, 109 | "min_connection": 0.0, 110 | "min_copper_edge_clearance": 0.0, 111 | "min_hole_clearance": 0.25, 112 | "min_hole_to_hole": 0.25, 113 | "min_microvia_diameter": 0.19999999999999998, 114 | "min_microvia_drill": 0.09999999999999999, 115 | "min_resolved_spokes": 2, 116 | "min_silk_clearance": 0.0, 117 | "min_text_height": 0.7999999999999999, 118 | "min_text_thickness": 0.08, 119 | "min_through_hole_diameter": 0.3, 120 | "min_track_width": 0.0, 121 | "min_via_annular_width": 0.09999999999999999, 122 | "min_via_diameter": 0.5, 123 | "solder_mask_clearance": 0.0, 124 | "solder_mask_min_width": 0.0, 125 | "solder_mask_to_copper_clearance": 0.0, 126 | "use_height_for_length_calcs": true 127 | }, 128 | "teardrop_options": [ 129 | { 130 | "td_allow_use_two_tracks": true, 131 | "td_curve_segcount": 5, 132 | "td_on_pad_in_zone": false, 133 | "td_onpadsmd": true, 134 | "td_onroundshapesonly": false, 135 | "td_ontrackend": false, 136 | "td_onviapad": true 137 | } 138 | ], 139 | "teardrop_parameters": [ 140 | { 141 | "td_curve_segcount": 0, 142 | "td_height_ratio": 1.0, 143 | "td_length_ratio": 0.5, 144 | "td_maxheight": 2.0, 145 | "td_maxlen": 1.0, 146 | "td_target_name": "td_round_shape", 147 | "td_width_to_size_filter_ratio": 0.9 148 | }, 149 | { 150 | "td_curve_segcount": 0, 151 | "td_height_ratio": 1.0, 152 | "td_length_ratio": 0.5, 153 | "td_maxheight": 2.0, 154 | "td_maxlen": 1.0, 155 | "td_target_name": "td_rect_shape", 156 | "td_width_to_size_filter_ratio": 0.9 157 | }, 158 | { 159 | "td_curve_segcount": 0, 160 | "td_height_ratio": 1.0, 161 | "td_length_ratio": 0.5, 162 | "td_maxheight": 2.0, 163 | "td_maxlen": 1.0, 164 | "td_target_name": "td_track_end", 165 | "td_width_to_size_filter_ratio": 0.9 166 | } 167 | ], 168 | "track_widths": [], 169 | "via_dimensions": [], 170 | "zones_allow_external_fillets": false 171 | }, 172 | "layer_presets": [], 173 | "viewports": [] 174 | }, 175 | "boards": [], 176 | "cvpcb": { 177 | "equivalence_files": [] 178 | }, 179 | "erc": { 180 | "erc_exclusions": [], 181 | "meta": { 182 | "version": 0 183 | }, 184 | "pin_map": [ 185 | [ 186 | 0, 187 | 0, 188 | 0, 189 | 0, 190 | 0, 191 | 0, 192 | 1, 193 | 0, 194 | 0, 195 | 0, 196 | 0, 197 | 2 198 | ], 199 | [ 200 | 0, 201 | 2, 202 | 0, 203 | 1, 204 | 0, 205 | 0, 206 | 1, 207 | 0, 208 | 2, 209 | 2, 210 | 2, 211 | 2 212 | ], 213 | [ 214 | 0, 215 | 0, 216 | 0, 217 | 0, 218 | 0, 219 | 0, 220 | 1, 221 | 0, 222 | 1, 223 | 0, 224 | 1, 225 | 2 226 | ], 227 | [ 228 | 0, 229 | 1, 230 | 0, 231 | 0, 232 | 0, 233 | 0, 234 | 1, 235 | 1, 236 | 2, 237 | 1, 238 | 1, 239 | 2 240 | ], 241 | [ 242 | 0, 243 | 0, 244 | 0, 245 | 0, 246 | 0, 247 | 0, 248 | 1, 249 | 0, 250 | 0, 251 | 0, 252 | 0, 253 | 2 254 | ], 255 | [ 256 | 0, 257 | 0, 258 | 0, 259 | 0, 260 | 0, 261 | 0, 262 | 0, 263 | 0, 264 | 0, 265 | 0, 266 | 0, 267 | 2 268 | ], 269 | [ 270 | 1, 271 | 1, 272 | 1, 273 | 1, 274 | 1, 275 | 0, 276 | 1, 277 | 1, 278 | 1, 279 | 1, 280 | 1, 281 | 2 282 | ], 283 | [ 284 | 0, 285 | 0, 286 | 0, 287 | 1, 288 | 0, 289 | 0, 290 | 1, 291 | 0, 292 | 0, 293 | 0, 294 | 0, 295 | 2 296 | ], 297 | [ 298 | 0, 299 | 2, 300 | 1, 301 | 2, 302 | 0, 303 | 0, 304 | 1, 305 | 0, 306 | 2, 307 | 2, 308 | 2, 309 | 2 310 | ], 311 | [ 312 | 0, 313 | 2, 314 | 0, 315 | 1, 316 | 0, 317 | 0, 318 | 1, 319 | 0, 320 | 2, 321 | 0, 322 | 0, 323 | 2 324 | ], 325 | [ 326 | 0, 327 | 2, 328 | 1, 329 | 1, 330 | 0, 331 | 0, 332 | 1, 333 | 0, 334 | 2, 335 | 0, 336 | 0, 337 | 2 338 | ], 339 | [ 340 | 2, 341 | 2, 342 | 2, 343 | 2, 344 | 2, 345 | 2, 346 | 2, 347 | 2, 348 | 2, 349 | 2, 350 | 2, 351 | 2 352 | ] 353 | ], 354 | "rule_severities": { 355 | "bus_definition_conflict": "error", 356 | "bus_entry_needed": "error", 357 | "bus_to_bus_conflict": "error", 358 | "bus_to_net_conflict": "error", 359 | "conflicting_netclasses": "error", 360 | "different_unit_footprint": "error", 361 | "different_unit_net": "error", 362 | "duplicate_reference": "error", 363 | "duplicate_sheet_names": "error", 364 | "endpoint_off_grid": "warning", 365 | "extra_units": "error", 366 | "global_label_dangling": "warning", 367 | "hier_label_mismatch": "error", 368 | "label_dangling": "error", 369 | "lib_symbol_issues": "warning", 370 | "missing_bidi_pin": "warning", 371 | "missing_input_pin": "warning", 372 | "missing_power_pin": "error", 373 | "missing_unit": "warning", 374 | "multiple_net_names": "warning", 375 | "net_not_bus_member": "warning", 376 | "no_connect_connected": "warning", 377 | "no_connect_dangling": "warning", 378 | "pin_not_connected": "error", 379 | "pin_not_driven": "error", 380 | "pin_to_pin": "warning", 381 | "power_pin_not_driven": "error", 382 | "similar_labels": "warning", 383 | "simulation_model_issue": "ignore", 384 | "unannotated": "error", 385 | "unit_value_mismatch": "error", 386 | "unresolved_variable": "error", 387 | "wire_dangling": "error" 388 | } 389 | }, 390 | "libraries": { 391 | "pinned_footprint_libs": [], 392 | "pinned_symbol_libs": [] 393 | }, 394 | "meta": { 395 | "filename": "Fossa PCB.kicad_pro", 396 | "version": 1 397 | }, 398 | "net_settings": { 399 | "classes": [ 400 | { 401 | "bus_width": 12, 402 | "clearance": 0.2, 403 | "diff_pair_gap": 0.25, 404 | "diff_pair_via_gap": 0.25, 405 | "diff_pair_width": 0.2, 406 | "line_style": 0, 407 | "microvia_diameter": 0.3, 408 | "microvia_drill": 0.1, 409 | "name": "Default", 410 | "pcb_color": "rgba(0, 0, 0, 0.000)", 411 | "schematic_color": "rgba(0, 0, 0, 0.000)", 412 | "track_width": 0.25, 413 | "via_diameter": 0.8, 414 | "via_drill": 0.4, 415 | "wire_width": 6 416 | } 417 | ], 418 | "meta": { 419 | "version": 3 420 | }, 421 | "net_colors": null, 422 | "netclass_assignments": null, 423 | "netclass_patterns": [] 424 | }, 425 | "pcbnew": { 426 | "last_paths": { 427 | "gencad": "", 428 | "idf": "", 429 | "netlist": "", 430 | "specctra_dsn": "", 431 | "step": "Fossa PCB.step", 432 | "vrml": "" 433 | }, 434 | "page_layout_descr_file": "" 435 | }, 436 | "schematic": { 437 | "annotate_start_num": 0, 438 | "drawing": { 439 | "dashed_lines_dash_length_ratio": 12.0, 440 | "dashed_lines_gap_length_ratio": 3.0, 441 | "default_line_thickness": 6.0, 442 | "default_text_size": 50.0, 443 | "field_names": [], 444 | "intersheets_ref_own_page": false, 445 | "intersheets_ref_prefix": "", 446 | "intersheets_ref_short": false, 447 | "intersheets_ref_show": false, 448 | "intersheets_ref_suffix": "", 449 | "junction_size_choice": 3, 450 | "label_size_ratio": 0.375, 451 | "pin_symbol_size": 25.0, 452 | "text_offset_ratio": 0.15 453 | }, 454 | "legacy_lib_dir": "", 455 | "legacy_lib_list": [], 456 | "meta": { 457 | "version": 1 458 | }, 459 | "net_format_name": "", 460 | "page_layout_descr_file": "", 461 | "plot_directory": "", 462 | "spice_current_sheet_as_root": false, 463 | "spice_external_command": "spice \"%I\"", 464 | "spice_model_current_sheet_as_root": true, 465 | "spice_save_all_currents": false, 466 | "spice_save_all_voltages": false, 467 | "subpart_first_id": 65, 468 | "subpart_id_separator": 0 469 | }, 470 | "sheets": [ 471 | [ 472 | "c3450d40-ed02-43ae-8c01-4c6e23902fd9", 473 | "" 474 | ] 475 | ], 476 | "text_variables": {} 477 | } 478 | -------------------------------------------------------------------------------- /PSDs/FOSSA - Primary components.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/PSDs/FOSSA - Primary components.psd -------------------------------------------------------------------------------- /PSDs/FOSSA - Primary wiring reference.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/PSDs/FOSSA - Primary wiring reference.psd -------------------------------------------------------------------------------- /PSDs/FOSSA - The FOSS ATM.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/PSDs/FOSSA - The FOSS ATM.psd -------------------------------------------------------------------------------- /PSDs/README.md: -------------------------------------------------------------------------------- 1 | ## PhotoShop Files 2 | 3 | ### FOSSA - The FOSS ATM 4 | 5 | This is the heading image for the repo. 6 | 7 | ### FOSSA - Primary components 8 | 9 | Overview visual of the main components used. 10 | 11 | ### FOSSA - Primary wiring reference 12 | 13 | Wiring and pin labelling for the main components. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://github.com/user-attachments/assets/a38bac69-6a07-4f44-831e-a95966f420ba) 2 | 3 | Welcome to the FOSSA project! FOSSA is an innovative, open-source Bitcoin Lightning ATM designed to be cost-effective and easy to build. Utilizing standard components such as an ESP32 microcontroller, a multi-coin acceptor, and a bill acceptor, FOSSA allows anyone to convert loose fiat change into Bitcoin efficiently. The project includes comprehensive instructions and resources, making it accessible for hobbyists and developers to create a fully functional Bitcoin ATM. Dive in and get started building your own FOSSA! 4 | 5 | ## Demo 6 | 7 | https://twitter.com/arcbtc/status/1567639231333277697 8 | 9 | ## Bill of Materials 10 | 11 | The parts for this ATM should be approximately £200-£250! 12 | 13 | You can run the FOSSA as a coin machine, a bill acceptor, or both. 14 | 15 | ### Primary FOSSA components 16 | 17 | | Part | Description | Buy (UK) | Estimated cost | 18 | |-------------|-------------|-------------|-------------| 19 | | ESP32 WT32-SC01 | This is the ESP32 touchscreen. You don't need any Wi-Fi. It should be supplied with a USB-C to USB cable. Note that the ESP32 WT32-SC01 is different from the ESP32 WT32-SC01 PLUS! | Ebay or [AliExpress](https://www.aliexpress.com/item/1005003191471709.html) | **£45** | 20 | | Option: NV10USB+ bill acceptor | The bill acceptor option to accept cash notes. To accept GBP, you should request that it be set to GBP and SIO mode when purchasing. | [Ebay (second hand)](https://www.ebay.co.uk/itm/263398460026) | **£70-£150** | 21 | | Option: DG600F(S) Multi Coin Acceptor | The coin acceptor option. The "S" denotes the front panel (stainless steel sheet version, where we can 3D print a black panel for it). | [AliExpress](https://www.aliexpress.com/w/wholesale-DG600F(S)-Multi-Coin-Acceptor.html?spm=a2g0o.home.search.0) or eBay | **£30** | 22 | | Screw terminal block | Easily connect the wires without soldering. | [Amazon (UK)](https://www.amazon.co.uk/gp/product/B08LNWMMHQ) | **£1** | 23 | | 12v power supply | Best to get one with terminal block adapter (12v battery also works well, for unplugged version). | [Amazon (UK)](https://www.amazon.co.uk/gp/product/B09MTBTXDJ/) | **£8** | 24 | | 12v to 5v step down converter with USB | 12V to 5V: The adapter converter module can convert unstable voltage DC 12V to stable DC 5V / 15W 3A output. | [Amazon (UK)](https://www.amazon.co.uk/gp/product/B07QQ587K3) | **£5** 25 | | Male-to-female GPIO jumpers | Plug and play GPIO connectors | [Amazon (UK)](https://www.amazon.co.uk/gp/product/B09MJZDXBB) | **£5** 26 | | Option: Aluminum "medicine box" | Cheap medicine box for the cash machine | Amazon (UK) | **£30** | 27 | | Option: Amazon Basic Home Safe | More secure box | Amazon (UK) | **£70** | 28 | | Option: 58mm 701 USB Thermal Receipt Printer | Print receipts for users. **NB The 3D printed enclosure does not currently fit this**. | [Ebay (UK)](https://www.ebay.co.uk/itm/293668439213) | **£25** | 29 | 30 | ![image](https://github.com/user-attachments/assets/35098b91-8d00-4885-bc16-ace9bb804597) 31 | 32 | ### 3D printed enclosure fixtures & fittings 33 | 34 | For the 3D-printed version, you will need: 35 | 36 | | Part | Description | Buy (UK) | 37 | |-------------|-------------|-------------| 38 | | M4 x 16mm pan head security torx tamper-resistant bolts | These hold the front facia to the back box. Anything longer than 14mm - 55mm is fine. | [Amazon (UK)](https://www.amazon.co.uk/gp/product/B0BTTRPBQV/) | 39 | | Tamper-proof torx wrench (T20) | To tighten the front facia Torx bolts. | [Amazon (UK)](https://www.amazon.co.uk/gp/product/B0D53LNNRW) | 40 | | Brass insert nuts, M4 x 6mm L x 6mm OD | Female threaded heat set knurled embedded insert nuts for 3D printing. These are stronger than trying to create threads in the 3D print. Using a soldering iron, these are heated up and pushed into the backbox for better attachment of the facia. | [Amazon (UK)](https://www.amazon.co.uk/gp/product/B09MCVW4GQ/) | 41 | | M4 x 30mm carriage/coach bolts | These are to replace the bolts supplied with the coin acceptor to accommodate the thickness of the facia. | [eBay (UK)](https://www.ebay.co.uk/itm/386550912089?var=653926055961) | 42 | | 5.5mm x 2.1mm DC Power Jack Socket Female Panel Mount Connector | This allows the 12V adaptor to plug into the ATM's outside. The box assumes a diameter of 7.7mm (like these). | [Amazon (UK)](https://www.amazon.co.uk/dp/B01N3679B8) | 43 | | 2.2mm Quick Disconnect Female Spade Connector | This allows us to crimp a cable and connect to the 5.5mm x 2.1mm DC Power Jack Socket | | 44 | | Cabinet draw tubular 16mm lock | This will be mounted on the door. | [Amazon (UK)](https://www.amazon.co.uk/Litensh-Tubular-Cylinder-Cupboard-Letterbox/dp/B09SHWXWXC/) | 45 | | Optional: Rubber feet (D30x22xH15) | If you want to have this one on the table, some rubber feet will go at the bottom of the backbox with an M5 bolt. You can also use adhesive versions. Grooves in the bottom of the box will support up to 30mm in diameter. | [Amazon (UK)](https://www.amazon.co.uk/dp/B0CQ23M5Z5) | 46 | | Optional: M5 x 16mm hex socket bolts | To screw the rubber feet into the back box. | [Amazon (UK)](https://www.amazon.co.uk/16mm-Socket-Button-Head-Screws/dp/B09BG8XNM7) | 47 | | Optional: M5 nylon nuts | To screw the rubber feet into the backbox. | [Amazon (UK)](https://www.amazon.co.uk/Bolt-Base-Stainless-Insert-Nylock) | 48 | | Optional: Adhesive security plate and locking cable | Secure the ATM when desk mounted. | [Amazon (UK)](https://www.amazon.co.uk/I3C-Security-Anti-Theft-Hardware-Smartphone/dp/B07FM93JL6) | 49 | 50 | ## Build 51 | 52 | A video tutorial is available here on how to construct the FOSSA: 53 | 54 | [https://www.youtube.com/watch?v=vbyYb9Yiu_k](https://www.youtube.com/watch?v=vbyYb9Yiu_k) 55 | 56 | ### Step 1: Hardware Setup 57 | 58 | > **STAY TUNED FOR A WIRING HARNESS TO MAKE THIS PLUG-AND-PLAY, AND YOU CAN SKIP TO STEP 2.** 59 | 60 | This method requires no soldering. We do this by using GPIO jumpers and terminal blocks. First, split a terminal block into a block of 3 (our LIVE terminal) and a block of 4 (our GROUND terminal). 61 | 62 | The wiring reference is as follows: 63 | 64 | ![image](https://github.com/user-attachments/assets/3bea19e4-7fa1-449f-b15d-e87d2377bd6c) 65 | 66 | **To connect the bill acceptor**: 67 | 68 | 1. Connect the Rx (GPIO 32) and Tx (GPIO 33), pins 29 and 31, on the WT32-SC01 board to pins 1 and 5, respectively, on the bill acceptor. 69 | 2. Using a GPIO jumper, connect the live wire (pin 15) to the first LIVE terminal block at an available terminal. 70 | 3. Using a GPIO jumper, connect the ground wire (pin 16) to a second GROUND terminal block of block at an available terminal. 71 | 4. Connect the positive wire (red) of the 12V to 5V power converter and connect it to the LIVE terminal block at an available terminal. 72 | 5. Connect the negative wire (black) of the 12V to 5V power converter and connect it to the GROUND terminal block at an available terminal. 73 | 6. Connect the ground pin (e.g. pin 5) of the WT32-SC01 and connect it to the 2nd terminal of the GROUND terminal block at an available terminal. 74 | 75 | | NV10USB+ Pin | WT32-SC01 Pin No. (Not GPIO No.) | Power Supply | 76 | |----------|-----------|----------------| 77 | | 1 (Tx) | 31 | N/A | 78 | | 5 (Rx) | 29 | N/A | 79 | | 15 (12V DC+) | N/A | 12V DC+ | 80 | | 16 (GND) | 5 | GND | 81 | 82 | > The bill acceptor needs to be programmed to your currency and set to `SIO` mode. Usually, you can buy them preconfigured. If you have to program, buy this wire and download the Validator Manager software here or here (sadly only runs on windows, so use a friends machine). Details on programming can be found here. It's relatively straightforward to program the acceptor: plug in the USB host cable, turn it on by holding the config button for 2 seconds, and open the Validator Manager software. If you prefer to build your own programming cable, see page 42 of the NV10 USB Operations Manual, which gives a wiring diagram for the NV10 USB host cable. You can ignore all the hardware requirements for programming in the guide; you just need the host cable! Don't try using the programming cards in the guide; that's an old system these machines no longer support - "Many Bothans died to bring us this information"! 83 | 84 | **To connect the coin acceptor**: 85 | 86 | > NB You can usually order the coin acceptor pre-programmed to your currencies. Otherwise, you will need to train the acceptor using this guide. 87 | 88 | 1. Set the 3rd dip switch to high (this sends integers to the WT32-SC01 rather than pulses). 89 | 2. Using a GPIO jumper, connect the interrupt pin (pin 5) on the coin accepter to the 5V pin (pin 2) on the WT32-SC01. 90 | 3. Using a GPIO jumper, connect the serial out (pin 2) on the coin accepter to pin 4 on the WT32-SC01. 91 | 4. Using a GPIO jumper, connect the live pin (pin 1) to the LIVE terminal block at an available terminal. 92 | 5. Using a GPIO jumper, connect the GND pin (pin 3) on the coin accepter to the GROUND terminal block at an available terminal. 93 | 94 | | DG600F(S) Pin | WT32-SC01 Pin No. (Not GPIO No.) | Power Supply 95 | | ------------- | ------------- | ------------- | 96 | | 5 | 2 | N/A | 97 | | 4 | N/A | N/A | 98 | | 3 (GND) | N/A | GROUND 99 | | 2 (serial out) | 4 | N/A 100 | | 1 (12V) | N/A | LIVE 101 | 102 | **To connect the 12V power supply**: 103 | 104 | 1. Connect a GPIO jumper to the live connection (+) on the 12V power supply terminal block adapter to the LIVE terminal block at an available terminal. 105 | 2. Connect a GPIO jumper to the ground connection (-) on the 12V power supply terminal block adapter to the GROUND terminal block at an available terminal. 106 | 107 | **To complete the terminal blocks**: 108 | 109 | 1. Connect each terminal in the LIVE terminal block so they all receive power by looping over wires from terminals 1 to 2 and 1 to 3. 110 | 2. Connect each terminal in the GROUND terminal block so they all receive power by looping over wires from terminals 1 to 2, 1 to 3 and 1 to 4. 111 | 112 | **To connect to power**: 113 | 114 | 1. Plug in the WT32-SC01 to the USB connector of the 12V to 5V power converter. 115 | 2. Plug in the connection terminal of the 12V power supply and connect to mains power. 116 | 117 | You should hear the bill acceptor and coin acceptors turn on. 118 | 119 | ### Step 2: Configure LNBits 120 | 121 | To configure your LNBits instance to pull funds from: 122 | 123 | 1. Login to your instance of LNBits. 124 | 2. Create a wallet. 125 | 3. Go to `Manage Extensions`, find `LNURLDevice`, and install/enable it. 126 | 4. Open the `LNURLDevice` extension, click `New LNURLDevice Instance`, give it the title of `ATM`, and choose the appropriate wallet for the ATM. Select the correct currency (e.g., `GBP`), `ATM` mode, and a percentage for the commission. 127 | 5. Copy the link it gives you. 128 | 129 | ### Step 3: Programming the WT32-SC01 130 | 131 | The ATM is configured in two parts: the ATM and LNBits running on a separate node. 132 | 133 | To set up your Arduino IDE: 134 | 135 | 1. If you have not already done so, install the Arduino IDE from [https://www.arduino.cc/en/Main/Software](https://www.arduino.cc/en/Main/Software). 136 | 2. Install the ESP32 libraries. You can do this by copying the stable release version link, currently `https://espressif.github.io/arduino-esp32/package_esp32_index.json` and putting in the `File` > `Preferences...` > `Additional Board Manager URLs` (see: [https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html)) 137 | 3. Install the boards by going to `Tools` > `Board` > `Board Manager...`, searching for ESP32, choose 2.0.17, and click Install. 138 | 4. Copy the libraries from this project libraries folder to your `"/Arduino/libraries"` folder (usually in OS `"Home"` directory) 139 | 5. Clone the repo and open accessPointFOSSA. 140 | 6. Select the relevant port and ESP32 dev module. 141 | 7. Go to `Sketch` > `Upload`. 142 | 8. Once uploaded, the access portal will launch when you press the screen on the WT32-SC01 device. 143 | 144 | To configure the ATM: 145 | 146 | 1. Connect the access portal wi-fi (`Device-fa7ce5a4`, for example). 147 | 2. Open the access portal. 148 | 2. Enter a new password. 149 | 3. Enter the coin denominations (if you are using a coin acceptor, separated by a comma) 150 | 4. Enter the maximum withdrawal amount in fiat (e.g. `30` for 30 GBP). 151 | 5. Enter a percentage charge for the service (e.g. `10` for 10%, which should be the same value you entered into LNBits). 152 | 6. Click `Save`. 153 | 7. unplug and plug the WT32-SC01 back in once saved. You should see the `Fiat for Sats` screen. 154 | 155 | ### Step 4: Enclosure 156 | 157 | > It should be pointed out that this ATM is designed to be attended to (such as on a shop counter) rather than something that can be left unattended and mounted to a wall. 158 | 159 | #### Option A: 3D printed enclosure 160 | 161 | If you want to want to print your own enclosure, we recommend these settings: 162 | 163 | 1. Print the Facia 164 | - STL: [FOSSA ATM Facia](https://github.com/lnbits/fossa/blob/main/3DPrints/FOSSA%20ATM%20Facia.stl) 165 | - Material: PETG 166 | - Layer: 0.2mm 167 | - Infill: 10-100% (recommend 20%+) 168 | - Supports: Yes 169 | - Notes: To set the Bitcoin logo with a different color, set a color change at layer 114 (13.20mm). In PrucaSlicer, you can "paint" out all the screw holes as they do not need supports. I would also recommend setting a thicker layer parameter, in PrucaSlicer, you can do this by settings `Printer Settings` > `Layers and perimeters` > `Perimeters` to `4`) 170 | 3. Print the backbox 171 | - STL: [FOSSA ATM Backbox](https://github.com/lnbits/fossa/blob/main/3DPrints/FOSSA%20ATM%20Back%20Box.stl) 172 | - Material: PETG 173 | - Layer: 0.2mm 174 | - Infill: 10-100% (recommend 20%+) 175 | - Supports: Yes 176 | - Notes: In PrusaSlicer you can set `Print Settings` > `Support material` > `First layer expansion` to `1 mm`, `XY separation between an object and its support` to `80%` and `Overhang threshold` to `1 mm`, or, you can "paint" out all but the door as the model has been designed for minimal supports 177 | 4. Print the accessories 178 | - STL: [FOSSA ATM WT-32-SC01 Backplate](https://github.com/lnbits/fossa/blob/main/3DPrints/FOSSA%20ATM%20WT-32-SC01%20Backplate.stl), [FOSSA ATM Coin Slot Cover](https://github.com/lnbits/fossa/blob/main/3DPrints/FOSSA%20ATM%20Coin%20Slot%20Cover.stl), [FOSSA ATM 4mm Lock Lever Shim](https://github.com/lnbits/fossa/blob/main/3DPrints/FOSSA%20ATM%204mm%20Lock%20Lever%20Shim.stl), [FOSSA ATM Door](https://github.com/lnbits/fossa/blob/main/3DPrints/FOSSA%20ATM%204mm%20Door.stl), [FOSSA ATM Backbox Note Holder](https://github.com/lnbits/fossa/blob/main/3DPrints/FOSSA%20ATM%20Backbox%20Note%20Holder.stl) 179 | - Material: PETG 180 | - Layer: 0.2mm 181 | - Infill: 10-100% (recommend 20%+) 182 | - Supports: No 183 | 5. Use a soldering iron to melt the brass female embedded insert nuts into the six holes for the facia (see picture 1 below) 184 | 6. OPTIONAL: If using the coin acceptor, replace the coach bolts with longer bolts (M4 x 30mm carriage/coach bolts) to account for the thickness of the facia. 185 | 7. Mount the bill acceptor so it is "smiling" (see picture 2) with the supplied mounting bracket and screws as pictured (see picture 3). You can remove the bill input feed from the main part of the unit and then re-attach it. 186 | 8. Mount the screen to the WT-32-SC01 Backplate. 187 | 9. Mount the backplate to the Facia with the M4 (4mm x 8mm Inc Head) hex socket countersunk screws. 188 | 10. Mount the backbox note holder to the backbox with the M4 (4mm x 8mm Inc Head) hex socket countersunk screws. 189 | 11. Mount the 5.5mm x 2.1mm DC Power Jack Socket Female Panel Mount Connector in the hole in the back left of the backbox (this directs the notes to the bottom of the box. 190 | 12. Connect wires from the connection terminal to the 2.2mm Quick Disconnect Female Spade Connector and plug into the inside of the 5.5mm x 2.1mm DC Power Jack Socket Female Panel Mount Connector. 191 | 13. OPTIONAL: Mount the Rubber feet (D30x22xH15) using the M5 x 16mm hex socket bolts and M5 nylon nuts (be careful not to over-tighten). 192 | 14. Using the Tamper-proof torx wrench (T20), use the screw in the M4 x 16mm pan head security torx tamper-resistant bolts to secure the facia to the backbox. 193 | 15. Plug the power supply in. 194 | 195 | 196 | 197 | 198 | 199 | #### Option B: Mount in a box 200 | 201 | Use the templates provided here, print out at 100% on standard UK A4, and check the dimensions are correct after printing. It's helpful if the bill acceptor and coin mech pins are accessible. 202 | 203 | * For the `Aluminium Storage Box` solution, holes can be cut with a sharp knife (clearly not secure - but acceptable for somewhere you can keep an eye on the ATM or for demos). 204 | 205 | * For the `Home Safe` solution, holes can be cut with an angle grinder and a very thin cutter. (If you have not used an angle grinder before, don't be scared; they're cheap, easy enough to use, and very useful. Take your time and wear safety equipment.) 206 | 207 | > We use CT1 sealant/adhesive (or similar) for mounting the screen, although the screen has screw points, which should prob be used for added security. 208 | 209 | ## Get in touch 210 | 211 | And there you have it. Have fun, and tag us on Twitter / Nostr with all your cool setups! 212 | 213 | > Join our telegram support/chat. 214 | 215 | ## Development 216 | 217 | ### build from arduino-cli 218 | ```bash 219 | sh build.sh 220 | ``` 221 | 222 | ### build, upload and monitor from arduino-cli 223 | ```bash 224 | sh debug.sh /dev/ttyUSB0 225 | ``` 226 | -------------------------------------------------------------------------------- /build-webinstaller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PROJECT_NAME=fossa 3 | REPO=https://github.com/lnbits/fossa/releases/download 4 | INSTALLER_PATH=./hardware-installer/public/firmware 5 | 6 | git clone https://github.com/lnbits/hardware-installer 7 | 8 | cp INSTALLER.md ./hardware-installer/public/INSTALLER.md 9 | cp versions.json ./hardware-installer/src/versions.json 10 | cp config.js ./hardware-installer/src/config.js 11 | 12 | sed -i "s/%title%/$PROJECT_NAME/g" ./hardware-installer/index.html 13 | 14 | mkdir -p $INSTALLER_PATH 15 | for device in $(jq -r '.devices[]' ./hardware-installer/src/versions.json); do 16 | for version in $(jq -r '.versions[]' ./hardware-installer/src/versions.json); do 17 | mkdir -p $INSTALLER_PATH/$device/$version 18 | wget $REPO/$version/$PROJECT_NAME.ino.bin 19 | wget $REPO/$version/$PROJECT_NAME.ino.partitions.bin 20 | wget $REPO/$version/$PROJECT_NAME.ino.bootloader.bin 21 | mv $PROJECT_NAME* $INSTALLER_PATH/$device/$version 22 | done 23 | done 24 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command -v arduino-cli >/dev/null 2>&1 || { echo >&2 "arduino-cli not found. Aborting."; exit 1; } 3 | arduino-cli config --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json init --overwrite 4 | arduino-cli core update-index 5 | arduino-cli upgrade 6 | # uBitcoin is broken on esp32 3.x.x 7 | arduino-cli core install esp32:esp32@2.0.17 8 | arduino-cli lib install TFT_eSPI ArduinoJson uBitcoin JC_Button EspSoftwareSerial "Adafruit Thermal Printer Library" 9 | 10 | tft_config=$(sh ./tft_config_build_flags.sh) 11 | arduino-cli compile \ 12 | --build-property "build.partitions=min_spiffs" \ 13 | --build-property "upload.maximum_size=1966080" \ 14 | --build-property "build.extra_flags.esp32=${tft_config}" \ 15 | --library ./libraries/QRCode \ 16 | --build-path build --fqbn esp32:esp32:ttgo-lora32 fossa 17 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | export const addressesAndFiles = [ 2 | { 3 | address: "0x1000", 4 | fileName: "fossa.ino.bootloader.bin", 5 | }, 6 | { 7 | address: "0x8000", 8 | fileName: "fossa.ino.partitions.bin", 9 | }, 10 | { 11 | address: "0xE000", 12 | fileName: "boot_app0.bin", 13 | }, 14 | { 15 | address: "0x10000", 16 | fileName: "fossa.ino.bin", 17 | }, 18 | ]; 19 | 20 | export const configPath = "elements.json"; 21 | export const elements = [ 22 | { 23 | name: "config_lnurl", 24 | value: "", 25 | label: "Whole LNURLATM string from LNURLDevices extension", 26 | type: "text", 27 | }, 28 | { 29 | name: "config_bill_ints", 30 | value: "", 31 | label: "Bill denominations seperated by comma ie: 1,5,10,20,50,100", 32 | type: "text", 33 | }, 34 | { 35 | name: "config_coin_floats", 36 | value: "", 37 | label: "Coin floats seperated by comma ie: 0.01,0.02,0.05,0.1,0.5,1 - no more than 6", 38 | type: "text", 39 | }, 40 | { 41 | name: "config_charge", 42 | value: "20", 43 | label: "Percentage you will charge for the service ie 10 for 10 percent", 44 | type: "text", 45 | }, 46 | { 47 | name: "config_max_amount", 48 | value: "40", 49 | label: "Max amount allowed to be withdrawn in one transaction", 50 | type: "text", 51 | }, 52 | { 53 | name: "config_max_amount_reset", 54 | value: "500", 55 | label: "Max amout before the atm stops withdrawals and wait to be emptied", 56 | type: "text", 57 | }, 58 | { 59 | name: "config_printer", 60 | value: "false", 61 | label: "Set true if you have a thermal printer connected", 62 | type: "text", 63 | }, 64 | { 65 | name: "config_lang", 66 | value: "en", 67 | label: "2 letter language code. Supports en, es, fr, de, it, pt, pl, hu, tr, ro, fi, sv", 68 | type: "text", 69 | } 70 | ]; 71 | -------------------------------------------------------------------------------- /debug.sh: -------------------------------------------------------------------------------- 1 | if [ -z "$1" ]; then 2 | echo "Usage: ./debug.sh /dev/ttyUSB0" 3 | exit 1 4 | fi 5 | echo "User TFT Config: " 6 | cat tft_config.txt 7 | tft_config=$(sh ./tft_config_build_flags.sh) 8 | arduino-cli compile \ 9 | --library ./libraries/QRCode \ 10 | --build-property "build.partitions=min_spiffs" \ 11 | --build-property "upload.maximum_size=1966080" \ 12 | --build-property "build.extra_flags.esp32=${tft_config}" \ 13 | --build-path build --fqbn esp32:esp32:ttgo-lora32 fossa && \ 14 | arduino-cli upload --input-dir build --fqbn esp32:esp32:ttgo-lora32 -p $1 && \ 15 | arduino-cli monitor -p $1 -c baudrate=115200 16 | -------------------------------------------------------------------------------- /docs/Cutting template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/docs/Cutting template.pdf -------------------------------------------------------------------------------- /docs/NV10 operations manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/docs/NV10 operations manual.pdf -------------------------------------------------------------------------------- /docs/NV10 user manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/docs/NV10 user manual.pdf -------------------------------------------------------------------------------- /docs/Wiring diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/docs/Wiring diagram.pdf -------------------------------------------------------------------------------- /fossa-tests/README.md: -------------------------------------------------------------------------------- 1 | # Bare-bones useful tests 2 | 3 | For debugging whether you can talk to the bill acceptor, coin acceptor and printer. -------------------------------------------------------------------------------- /fossa-tests/testbills/testbills.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define RX1 32 // Bill acceptor serial in 4 | #define TX1 33 // Bill acceptor serial out 5 | HardwareSerial SerialPort1(1); 6 | void setup() { 7 | Serial.begin(115200); 8 | SerialPort1.begin(300, SERIAL_8N2, TX1, RX1); 9 | SerialPort1.write(184); 10 | } 11 | 12 | void loop() { 13 | int x = 0; 14 | x = SerialPort1.read(); 15 | if(x > 0 && x < 10){ 16 | Serial.print("Worked, got: "); 17 | Serial.print(x); 18 | Serial.println(" from bill acceptor (which is the number of the note in the notes list set using the validator software)"); 19 | } 20 | delay(500); 21 | } 22 | -------------------------------------------------------------------------------- /fossa-tests/testcoins/testcoins.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define TX2 4 // Coinmech serial out 4 | #define INHIBITMECH 2 5 | HardwareSerial SerialPort2(2); 6 | void setup() { 7 | Serial.begin(115200); 8 | SerialPort2.begin(4800, SERIAL_8N1, TX2); 9 | pinMode(INHIBITMECH, OUTPUT); 10 | digitalWrite(INHIBITMECH, LOW); 11 | delay(10000); 12 | digitalWrite(INHIBITMECH, HIGH); 13 | } 14 | 15 | void loop() { 16 | int x = 0; 17 | x = SerialPort2.read(); 18 | if(x > 0){ 19 | Serial.print("Worked, got: "); 20 | Serial.print(x); 21 | Serial.println(" from coin acceptor"); 22 | } 23 | delay(500); 24 | } 25 | -------------------------------------------------------------------------------- /fossa-tests/testprint/testprint.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #define RXP 22 // TX of the thermal printer 5 | #define TXP 23 // RX of the thermal printer 6 | SoftwareSerial printerSerial(RXP, TXP); 7 | Adafruit_Thermal printer(&printerSerial); 8 | 9 | void setup() { 10 | printerSerial.begin(9600); 11 | printer.begin(); 12 | printer.wake(); 13 | printer.setDefault(); 14 | printer.justify('C'); 15 | printer.feed(3); 16 | printer.boldOn(); 17 | printer.setSize('L'); 18 | printer.println("Working :)"); 19 | printer.sleep(); 20 | } 21 | 22 | void loop() { 23 | } -------------------------------------------------------------------------------- /fossa/100_config.ino: -------------------------------------------------------------------------------- 1 | String failedT, unableT, maxaT, willT, loadT, maxrT, printY, langT; 2 | 3 | void executeConfig() { 4 | while (true) { 5 | BTNA.read(); 6 | if (BTNA.wasReleased()) { 7 | readFiles(); 8 | return; 9 | } 10 | if (Serial.available() == 0) 11 | continue; 12 | String data = Serial.readStringUntil('\n'); 13 | Serial.println("received: " + data); 14 | KeyValue kv = extractKeyValue(data); 15 | String commandName = kv.key; 16 | if (commandName == "/config-done") { 17 | Serial.println("/config-done"); 18 | return; 19 | } 20 | executeCommand(commandName, kv.value); 21 | } 22 | } 23 | 24 | void executeCommand(String commandName, String commandData) { 25 | Serial.println("executeCommand: " + commandName + " > " + commandData); 26 | KeyValue kv = extractKeyValue(commandData); 27 | String path = kv.key; 28 | String data = kv.value; 29 | 30 | if (commandName == "/file-remove") { 31 | return removeFile(path); 32 | } 33 | if (commandName == "/file-append") { 34 | return appendToFile(path, data); 35 | } 36 | 37 | if (commandName == "/file-read") { 38 | Serial.println("prepare to read"); 39 | readFile(path); 40 | Serial.println("readFile done"); 41 | return; 42 | } 43 | 44 | Serial.println("command unknown"); 45 | } 46 | 47 | void removeFile(String path) { 48 | Serial.println("removeFile: " + path); 49 | SPIFFS.remove("/" + path); 50 | } 51 | 52 | void appendToFile(String path, String data) { 53 | Serial.println("appendToFile: " + path); 54 | File file = SPIFFS.open("/" + path, FILE_APPEND); 55 | if (!file) { 56 | file = SPIFFS.open("/" + path, FILE_WRITE); 57 | } 58 | if (file) { 59 | file.println(data); 60 | file.close(); 61 | } 62 | } 63 | 64 | void readFile(String path) { 65 | Serial.println("readFile: " + path); 66 | File file = SPIFFS.open("/" + path); 67 | if (file) { 68 | while (file.available()) { 69 | String line = file.readStringUntil('\n'); 70 | Serial.println("/file-read " + line); 71 | } 72 | file.close(); 73 | } 74 | Serial.println(""); 75 | Serial.println("/file-done"); 76 | } 77 | 78 | KeyValue extractKeyValue(String s) { 79 | int spacePos = s.indexOf(" "); 80 | String key = s.substring(0, spacePos); 81 | if (spacePos == -1) { 82 | return { key, "" }; 83 | } 84 | String value = s.substring(spacePos + 1, s.length()); 85 | return { key, value }; 86 | } 87 | 88 | void setDefaultValues() { 89 | Serial.println("Hardcoded settings:"); 90 | deviceString = DEVICE_STRING; 91 | Serial.println("Device string: " + deviceString); 92 | language = LANGUAGE; 93 | Serial.println("Language: " + language); 94 | charge = CHARGE; 95 | Serial.println("Charge: " + String(charge) + "%"); 96 | maxamount = MAX_AMOUNT; 97 | Serial.println("Max amount: " + String(maxamount)); 98 | maxBeforeReset = MAX_BEFORE_RESET; 99 | Serial.println("Max before reset: " + String(maxBeforeReset)); 100 | } 101 | 102 | void readFiles() { 103 | File paramFile = FlashFS.open(PARAM_FILE, "r"); 104 | if (!paramFile) { 105 | Serial.println("unable to open file, using default values"); 106 | setDefaultValues(); 107 | printMessage("", failedT, unableT, TFT_WHITE, TFT_BLACK); 108 | return; 109 | } 110 | StaticJsonDocument<1000> doc; 111 | DeserializationError error = deserializeJson(doc, paramFile.readString()); 112 | 113 | String LNURLConfig = getJsonValue(doc, "config_lnurl"); 114 | if (LNURLConfig != "") { 115 | deviceString = LNURLConfig; 116 | Serial.println("Device string: " + deviceString); 117 | } 118 | else{ 119 | Serial.println("LNURLdevice config not found, endless loop"); 120 | printMessage("", failedT, unableT, TFT_WHITE, TFT_BLACK); 121 | while (true) { 122 | } 123 | } 124 | 125 | String maxAmountConfig = getJsonValue(doc, "config_max_amount, using default"); 126 | if (maxAmountConfig != "") { 127 | maxamount = maxAmountConfig.toInt(); 128 | Serial.println("maxamount: " + maxAmountConfig); 129 | 130 | } else { 131 | maxamount = MAX_AMOUNT; 132 | printMessage("", maxaT, willT, TFT_WHITE, TFT_BLACK); 133 | delay(3000); 134 | tft.fillScreen(TFT_BLACK); 135 | } 136 | 137 | String chargeConfig = getJsonValue(doc, "config_charge"); 138 | if (chargeConfig != "") { 139 | charge = chargeConfig.toInt(); 140 | Serial.println("charge: " + chargeConfig); 141 | } else { 142 | charge = CHARGE; 143 | Serial.println("charge config not found, using default"); 144 | printMessage("", loadT, willT, TFT_WHITE, TFT_BLACK); 145 | delay(3000); 146 | tft.fillScreen(TFT_BLACK); 147 | } 148 | 149 | String maxBeforeResetConfig = getJsonValue(doc, "config_max_amount_reset"); 150 | if (maxBeforeResetConfig != "") { 151 | maxBeforeReset = maxBeforeResetConfig.toInt(); 152 | Serial.println("maxBeforeReset: " + maxBeforeResetConfig); 153 | } else { 154 | maxBeforeReset = MAX_BEFORE_RESET; 155 | Serial.println("maxBeforeReset config not found, using default"); 156 | printMessage("", maxrT, willT, TFT_WHITE, TFT_BLACK); 157 | delay(3000); 158 | tft.fillScreen(TFT_BLACK); 159 | } 160 | 161 | String billAmountString = getJsonValue(doc, "config_bill_ints"); 162 | convertStringToIntArray(billAmountString.c_str(), billAmountInt); 163 | 164 | String coinAmountString = getJsonValue(doc, "config_coin_floats"); 165 | convertStringToFloatArray(coinAmountString.c_str(), coinAmountFloat); 166 | 167 | String langConfig = getJsonValue(doc, "config_lang"); 168 | if (langConfig != "") { 169 | language = langConfig; 170 | Serial.println("language: " + langConfig); 171 | } 172 | else{ 173 | language = LANGUAGE; 174 | Serial.println("language config not found"); 175 | printMessage("", langT, willT, TFT_WHITE, TFT_BLACK); 176 | delay(3000); 177 | tft.fillScreen(TFT_BLACK); 178 | } 179 | 180 | paramFile.close(); 181 | } 182 | -------------------------------------------------------------------------------- /fossa/101_translations.ino: -------------------------------------------------------------------------------- 1 | // Supports en, es, fr, de, it, pt, pl, hu, tr, ro, fi, sv 2 | String translate(String key, String language) { 3 | const char* translations = R"( 4 | [en] 5 | usbT = USB config mode 6 | tapScreenT = TAP SCREEN WHEN FINISHED 7 | scanMeT = SCAN ME TAP SCREEN WHEN FINISHED 8 | totalT = Total: 9 | fossaT = FOSSA! Bitcoin ATM 10 | satsT = SATS 11 | forT = FOR 12 | fiatT = FIAT! 13 | feedT = feed me fiat. 14 | chargeT = % charge 15 | printingT = Printing 16 | waitT = please wait 17 | workingT = Working... 18 | thisVoucherT = This voucher can be redeemed for 19 | ofBitcoinT = of Bitcoin 20 | thankYouT = Thank you 21 | scanMeClaimT = Scan me with a Lightning wallet to get your Bitcoin 22 | tooMuchFiatT = Too much FIAT! 23 | contactOwnerT = Please contact owner 24 | failedT = FAILED 25 | unableT = unable to read lnurl 26 | maxaT = WARNING: max amount 27 | willT = Will use default 28 | loadT = WARNING: load charge 29 | maxrT = WARNING: max reset 30 | printY = WARNING: print bool 31 | langT = WARNING: lang fail 32 | 33 | [es] 34 | usbT = Modo de configuracion USB 35 | tapScreenT = TOQUE LA PANTALLA CUANDO TERMINE 36 | scanMeT = ESCANEME TOQUE LA PANTALLA CUANDO TERMINE 37 | totalT = Total: 38 | fossaT = FOSSA! Cajero Bitcoin 39 | satsT = SATS 40 | forT = PARA 41 | fiatT = FIAT! 42 | feedT = dame fiat. 43 | chargeT = % de carga 44 | printingT = Imprimiendo 45 | waitT = por favor espera 46 | workingT = Trabajando... 47 | thisVoucherT = Este vale se puede canjear por 48 | ofBitcoinT = de Bitcoin 49 | thankYouT = Gracias 50 | scanMeClaimT = Escaneame con una billetera Lightning para recibir tu Bitcoin 51 | tooMuchFiatT = Demasiado FIAT! 52 | contactOwnerT = Por favor contacta al propietario 53 | failedT = FALLIDO 54 | unableT = no se puede leer lnurl 55 | maxaT = ADVERTENCIA: cantidad maxima 56 | willT = Usara el valor predeterminado 57 | loadT = ADVERTENCIA: carga de valor 58 | maxrT = ADVERTENCIA: reinicio maximo 59 | printY = ADVERTENCIA: bool de impresion 60 | langT = ADVERTENCIA: error de idioma 61 | 62 | [fr] 63 | usbT = Mode de configuration USB 64 | tapScreenT = TOUCHEZ L ECRAN QUAND TERMINE 65 | scanMeT = SCANEZ MOI TOUCHEZ L ECRAN QUAND TERMINE 66 | totalT = Total: 67 | fossaT = FOSSA! Distributeur Bitcoin 68 | satsT = SATS 69 | forT = POUR 70 | fiatT = FIAT! 71 | feedT = nourrissez moi de fiat 72 | chargeT = % charge 73 | printingT = Impression 74 | waitT = veuillez patienter 75 | workingT = En cours... 76 | thisVoucherT = Ce bon peut etre echange contre 77 | ofBitcoinT = de Bitcoin 78 | thankYouT = Merci 79 | scanMeClaimT = Scannez moi avec un portefeuille Lightning pour obtenir votre Bitcoin 80 | tooMuchFiatT = Trop de FIAT ! 81 | contactOwnerT = Veuillez contacter le proprietaire 82 | failedT = ECHOUE 83 | unableT = impossible de lire lnurl 84 | maxaT = AVERTISSEMENT: montant maximal 85 | willT = Utilisera la valeur par defaut 86 | loadT = AVERTISSEMENT: charge de chargement 87 | maxrT = AVERTISSEMENT: reinitialisation maximale 88 | printY = AVERTISSEMENT: bool impression 89 | langT = AVERTISSEMENT: echec de langue 90 | 91 | [de] 92 | usbT = USB-Konfigurationsmodus 93 | tapScreenT = BERUEHREN SIE DEN BILDSCHIRM WENN FERTIG 94 | scanMeT = SCANNEN SIE MICH BERUEHREN SIE DEN BILDSCHIRM WENN FERTIG 95 | totalT = Gesamt: 96 | fossaT = FOSSA! Bitcoin-Geldautomat 97 | satsT = SATS 98 | forT = FUER 99 | fiatT = FIAT! 100 | feedT = fuettere mich mit Fiat 101 | chargeT = % Ladung 102 | printingT = Drucken 103 | waitT = bitte warten 104 | workingT = Arbeitet... 105 | thisVoucherT = Dieser Gutschein kann eingeloest werden fuer 106 | ofBitcoinT = von Bitcoin 107 | thankYouT = Danke 108 | scanMeClaimT = Scannen Sie mich mit einem Lightning Wallet um Ihr Bitcoin zu erhalten 109 | tooMuchFiatT = Zu viel FIAT! 110 | contactOwnerT = Bitte kontaktieren Sie den Besitzer 111 | failedT = FEHLGESCHLAGEN 112 | unableT = lnurl konnte nicht gelesen werden 113 | maxaT = WARNUNG: Maximalbetrag 114 | willT = Standard wird verwendet 115 | loadT = WARNUNG: Ladefracht 116 | maxrT = WARNUNG: Maximales Zuruecksetzen 117 | printY = WARNUNG: Druck bool 118 | langT = WARNUNG: Sprachfehler 119 | 120 | [it] 121 | usbT = Modalita di configurazione USB 122 | tapScreenT = TOCCA LO SCHERMO QUANDO FINITO 123 | scanMeT = SCANSIONAMI TOCCA LO SCHERMO QUANDO FINITO 124 | totalT = Totale: 125 | fossaT = FOSSA! Bancomat Bitcoin 126 | satsT = SATS 127 | forT = PER 128 | fiatT = FIAT! 129 | feedT = dammelo in fiat 130 | chargeT = % carica 131 | printingT = Stampa in corso 132 | waitT = attendere prego 133 | workingT = Funzionamento... 134 | thisVoucherT = Questo voucher puo essere riscattato per 135 | ofBitcoinT = di Bitcoin 136 | thankYouT = Grazie 137 | scanMeClaimT = Scansionami con un portafoglio Lightning per ottenere il tuo Bitcoin 138 | tooMuchFiatT = Troppo FIAT! 139 | contactOwnerT = Per favore contatta il proprietario 140 | failedT = FALLITO 141 | unableT = impossibile leggere lnurl 142 | maxaT = AVVERTENZA: importo massimo 143 | willT = Verra utilizzato il valore predefinito 144 | loadT = AVVERTENZA: carica di carico 145 | maxrT = AVVERTENZA: ripristino massimo 146 | printY = AVVERTENZA: stampa bool 147 | langT = AVVERTENZA: errore di lingua 148 | 149 | [pt] 150 | usbT = Modo de configuracao USB 151 | tapScreenT = TOQUE NA TELA QUANDO TERMINAR 152 | scanMeT = ESCANEIE ME TOQUE NA TELA QUANDO TERMINAR 153 | totalT = Total: 154 | fossaT = FOSSA! Caixa eletronico Bitcoin 155 | satsT = SATS 156 | forT = PARA 157 | fiatT = FIAT! 158 | feedT = me de fiat 159 | chargeT = % de carga 160 | printingT = Imprimindo 161 | waitT = por favor aguarde 162 | workingT = Trabalhando... 163 | thisVoucherT = Este voucher pode ser resgatado por 164 | ofBitcoinT = de Bitcoin 165 | thankYouT = Obrigado 166 | scanMeClaimT = Escaneie me com uma carteira Lightning para obter seu Bitcoin 167 | tooMuchFiatT = Muito FIAT! 168 | contactOwnerT = Por favor entre em contato com o proprietario 169 | failedT = FALHOU 170 | unableT = incapaz de ler lnurl 171 | maxaT = AVISO: valor maximo 172 | willT = Usara o valor padrao 173 | loadT = AVISO: carga de valor 174 | maxrT = AVISO: redefinicao maxima 175 | printY = AVISO: bool de impressao 176 | langT = AVISO: falha no idioma 177 | 178 | [pl] 179 | usbT = Tryb konfiguracji USB 180 | tapScreenT = DOTKNIJ EKRAN PO UKONCZENIU 181 | scanMeT = SCANUJ MNIE DOTKNIJ EKRAN PO UKONCZENIU 182 | totalT = Razem: 183 | fossaT = FOSSA! Bankomat Bitcoin 184 | satsT = SATS 185 | forT = DLA 186 | fiatT = FIAT! 187 | feedT = daj mi fiat 188 | chargeT = % ladunku 189 | printingT = Drukowanie 190 | waitT = prosze czekac 191 | workingT = Pracuje... 192 | thisVoucherT = Ten bon moze byc wymieniony na 193 | ofBitcoinT = Bitcoin 194 | thankYouT = Dziekuje 195 | scanMeClaimT = Zeskanuj mnie portfelem Lightning aby otrzymac swoj Bitcoin 196 | tooMuchFiatT = Za duzo FIAT! 197 | contactOwnerT = Prosze skontaktowac sie z wlascicielem 198 | failedT = NIEUDANE 199 | unableT = nie mozna odczytac lnurl 200 | maxaT = OSTRZEZENIE: maksymalna kwota 201 | willT = Zostanie uzyta wartosc domyslna 202 | loadT = OSTRZEZENIE: ladunek 203 | maxrT = OSTRZEZENIE: maksymalne zresetowanie 204 | printY = OSTRZEZENIE: bool drukowania 205 | langT = OSTRZEZENIE: blad jezyka 206 | 207 | [hu] 208 | usbT = USB konfiguracios mod 209 | tapScreenT = TAP IN EKRANRA HA VEGE 210 | scanMeT = SZKENNELJ ENGEM TAP IN EKRANRA HA VEGE 211 | totalT = Osszesen: 212 | fossaT = FOSSA! Bitcoin ATM 213 | satsT = SATS 214 | forT = SZAMARA 215 | fiatT = FIAT! 216 | feedT = etess meg fiatot 217 | chargeT = % toltes 218 | printingT = Nyomtatas 219 | waitT = kerlek varj 220 | workingT = Munkalkodik... 221 | thisVoucherT = Ez a voucher bevalthato 222 | ofBitcoinT = Bitcoin ertekere 223 | thankYouT = Koszonom 224 | scanMeClaimT = Szkenneld meg egy Lightning tarcaval hogy megkapd a Bitcoinod 225 | tooMuchFiatT = Tul sok FIAT! 226 | contactOwnerT = Kerlek lepj kapcsolatba a tulajdonossal 227 | failedT = HIBAS 228 | unableT = nem sikerult beolvasni az lnurl 229 | maxaT = FIGYELEM: max osszeg 230 | willT = Alapertelmezett ertek lesz hasznalva 231 | loadT = FIGYELEM: toltési arany 232 | maxrT = FIGYELEM: max visszaallitas 233 | printY = FIGYELEM: nyomtatas bool 234 | langT = FIGYELEM: nyelvi hiba 235 | 236 | [tr] 237 | usbT = USB ayar modu 238 | tapScreenT = BITTIGINDE EKRANA DOKUNUN 239 | scanMeT = BENI TARAMAK BITTIGINDE EKRANA DOKUNUN 240 | totalT = Toplam: 241 | fossaT = FOSSA! Bitcoin ATM 242 | satsT = SATS 243 | forT = ICIN 244 | fiatT = FIAT! 245 | feedT = fiat ver 246 | chargeT = % sarj 247 | printingT = Yazdiriliyor 248 | waitT = lutfen bekleyin 249 | workingT = Calisiyor... 250 | thisVoucherT = Bu fis sunun karsiliginda kullanilabilir 251 | ofBitcoinT = Bitcoin 252 | thankYouT = Tesekkur ederim 253 | scanMeClaimT = Bitcoin almak icin beni bir Lightning cuzdaniyla tarayin 254 | tooMuchFiatT = Cok fazla FIAT! 255 | contactOwnerT = Lutfen sahibi ile iletisime gecin 256 | failedT = BASARISIZ 257 | unableT = lnurl okunamiyor 258 | maxaT = UYARI: maksimum miktar 259 | willT = Varsayilan kullanilacak 260 | loadT = UYARI: yukleme sarji 261 | maxrT = UYARI: maksimum sifirlama 262 | printY = UYARI: yazdirma bool 263 | langT = UYARI: dil hatasi 264 | 265 | [ro] 266 | usbT = Mod configurare USB 267 | tapScreenT = ATINGE ECRANUL CAND GATA 268 | scanMeT = SCANEAZA MA ATINGE ECRANUL CAND GATA 269 | totalT = Total: 270 | fossaT = FOSSA! ATM Bitcoin 271 | satsT = SATS 272 | forT = PENTRU 273 | fiatT = FIAT! 274 | feedT = hraneste-ma cu fiat 275 | chargeT = % incarcare 276 | printingT = Imprimare 277 | waitT = te rog asteapta 278 | workingT = Se lucreaza... 279 | thisVoucherT = Acest voucher poate fi rascumparat pentru 280 | ofBitcoinT = de Bitcoin 281 | thankYouT = Multumesc 282 | scanMeClaimT = Scaneaza-ma cu un portofel Lightning pentru a primi Bitcoin-ul tau 283 | tooMuchFiatT = Prea mult FIAT! 284 | contactOwnerT = Va rugam sa contactati proprietarul 285 | failedT = ESUAT 286 | unableT = incapabil sa citeasca lnurl 287 | maxaT = AVERTISMENT: suma maxima 288 | willT = Va folosi valoarea implicita 289 | loadT = AVERTISMENT: incarcare tarifara 290 | maxrT = AVERTISMENT: resetare maxima 291 | printY = AVERTISMENT: bool imprimare 292 | langT = AVERTISMENT: esec de limba 293 | 294 | [fi] 295 | usbT = USB-moodi 296 | tapScreenT = KOSKETA NAYTTOA KUN VALMIS 297 | scanMeT = SKANNAA MINUT KOSKETA NAYTTOA KUN VALMIS 298 | totalT = Yhteensa: 299 | fossaT = FOSSA! Bitcoin-automatti 300 | satsT = SATS 301 | forT = VASTAAN 302 | fiatT = FIAT! 303 | feedT = anna minulle fiat 304 | chargeT = % lataus 305 | printingT = Tulostus 306 | waitT = ole hyva ja odota 307 | workingT = Toiminnassa... 308 | thisVoucherT = Tama kuponki voidaan lunastaa 309 | ofBitcoinT = Bitcoinista 310 | thankYouT = Kiitos 311 | scanMeClaimT = Skannaa minut Lightning-lompakolla saadaksesi Bitcoinisi 312 | tooMuchFiatT = Liikaa FIAT! 313 | contactOwnerT = Ole hyva ja ota yhteytta omistajaan 314 | failedT = EPAAONNISTUI 315 | unableT = ei pysty lukemaan lnurl 316 | maxaT = VAROITUS: enin maara 317 | willT = Oletusarvo kaytossa 318 | loadT = VAROITUS: latausmaksu 319 | maxrT = VAROITUS: enin nollaus 320 | printY = VAROITUS: tulosta bool 321 | langT = VAROITUS: kielivirhe 322 | 323 | [sv] 324 | usbT = USB-konfigurationslage 325 | tapScreenT = TRYCK PA SKARMEN NAR KLART 326 | scanMeT = SKANNA MIG TRYCK PA SKARMEN NAR KLART 327 | totalT = Totalt: 328 | fossaT = FOSSA! Bitcoin-automat 329 | satsT = SATS 330 | forT = FOR 331 | fiatT = FIAT! 332 | feedT = mata mig med fiat 333 | chargeT = % laddning 334 | printingT = Skriver ut 335 | waitT = var god vante 336 | workingT = Arbetar... 337 | thisVoucherT = Denna kupong kan losas in for 338 | ofBitcoinT = av Bitcoin 339 | thankYouT = Tack 340 | scanMeClaimT = Skanna mig med en Lightning-plambok for att fa dina Bitcoin 341 | tooMuchFiatT = For mycket FIAT! 342 | contactOwnerT = Var god kontakta agaren 343 | failedT = MISSLYCKADES 344 | unableT = kan inte lasa lnurl 345 | maxaT = VARNING: maxbelopp 346 | willT = Standardvardet anvands 347 | loadT = VARNING: laddningsavgift 348 | maxrT = VARNING: max aterstallning 349 | printY = VARNING: skriv ut bool 350 | langT = VARNING: sprakfel 351 | )"; 352 | 353 | String line; 354 | bool inCorrectLanguage = false; 355 | int startPos = 0; 356 | int endPos; 357 | 358 | while (startPos < strlen(translations)) { 359 | endPos = startPos; 360 | while (translations[endPos] != '\n' && translations[endPos] != '\0') { 361 | endPos++; 362 | } 363 | line = String(translations).substring(startPos, endPos); 364 | line.trim(); // Trim line 365 | startPos = endPos + 1; 366 | 367 | if (line.length() == 0) { 368 | continue; 369 | } 370 | 371 | // Check for language section 372 | if (line.startsWith("[")) { 373 | inCorrectLanguage = (line == "[" + language + "]"); 374 | continue; 375 | } 376 | 377 | if (inCorrectLanguage) { 378 | int separatorPos = line.indexOf('='); 379 | if (separatorPos != -1) { 380 | String currentKey = line.substring(0, separatorPos); 381 | currentKey.trim(); // Trim key separately 382 | 383 | String value = line.substring(separatorPos + 1); 384 | value.trim(); // Trim value separately 385 | 386 | if (currentKey == key) { 387 | return value; 388 | } 389 | } 390 | } 391 | } 392 | 393 | return key; // Return the key if no translation is found 394 | } 395 | 396 | void translateAll(String language) { 397 | usbT = translate("usbT", language); 398 | tapScreenT = translate("tapScreenT", language); 399 | scanMeT = translate("scanMeT", language); 400 | totalT = translate("totalT", language); 401 | fossaT = translate("fossaT", language); 402 | satsT = translate("satsT", language); 403 | forT = translate("forT", language); 404 | fiatT = translate("fiatT", language); 405 | feedT = translate("feedT", language); 406 | chargeT = translate("chargeT", language); 407 | printingT = translate("printingT", language); 408 | waitT = translate("waitT", language); 409 | workingT = translate("workingT", language); 410 | thisVoucherT = translate("chargeT", language); 411 | ofBitcoinT = translate("printingT", language); 412 | thankYouT = translate("waitT", language); 413 | scanMeClaimT = translate("workingT", language); 414 | tooMuchFiatT = translate("tooMuchFiatT", language); 415 | contactOwnerT = translate("contactOwnerT", language); 416 | failedT = translate("failedT", language); 417 | unableT = translate("unableT", language); 418 | maxaT = translate("maxaT", language); 419 | willT = translate("willT", language); 420 | loadT = translate("loadT", language); 421 | maxrT = translate("maxrT", language); 422 | printY = translate("printY", language); 423 | langT = translate("langT", language); 424 | } 425 | -------------------------------------------------------------------------------- /fossa/102_helpers.ino: -------------------------------------------------------------------------------- 1 | void to_upper(char *arr) { 2 | for (size_t i = 0; i < strlen(arr); i++) { 3 | if (arr[i] >= 'a' && arr[i] <= 'z') { 4 | arr[i] = arr[i] - 'a' + 'A'; 5 | } 6 | } 7 | } 8 | 9 | String getJsonValue(JsonDocument &doc, const char *name) { 10 | for (JsonObject elem : doc.as()) { 11 | if (strcmp(elem["name"], name) == 0) { 12 | String value = elem["value"].as(); 13 | return value; 14 | } 15 | } 16 | return ""; 17 | } 18 | 19 | String getValue(String data, char separator, int index) { 20 | int found = 0; 21 | int strIndex[] = { 0, -1 }; 22 | const int maxIndex = data.length() - 1; 23 | 24 | for (int i = 0; i <= maxIndex && found <= index; i++) { 25 | if (data.charAt(i) == separator || i == maxIndex) { 26 | found++; 27 | strIndex[0] = strIndex[1] + 1; 28 | strIndex[1] = (i == maxIndex) ? i + 1 : i; 29 | } 30 | } 31 | 32 | return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; 33 | } 34 | 35 | void splitSettings(String str) { 36 | int firstComma = str.indexOf(','); 37 | int secondComma = str.indexOf(',', firstComma + 1); 38 | baseURLATM = str.substring(0, firstComma); 39 | secretATM = str.substring(firstComma + 1, secondComma); 40 | currencyATM = str.substring(secondComma + 1); 41 | } 42 | 43 | void convertStringToFloatArray(const char* str, float* floatArray) { 44 | char buffer[20]; // Temporary buffer to hold each substring 45 | int index = 0; // Index for the float array 46 | int bufferIndex = 0; // Index for the buffer 47 | 48 | for (int i = 0; str[i] != '\0'; i++) { 49 | if (str[i] == ',') { // When a comma is found 50 | buffer[bufferIndex] = '\0'; // Null-terminate the buffer string 51 | floatArray[index] = atof(buffer); // Convert buffer to float and store in array 52 | index++; // Move to the next position in float array 53 | bufferIndex = 0; // Reset buffer index 54 | } else { 55 | buffer[bufferIndex++] = str[i]; // Copy characters to buffer 56 | } 57 | } 58 | 59 | // Don't forget to convert the last number in the string 60 | buffer[bufferIndex] = '\0'; // Null-terminate the buffer 61 | floatArray[index] = atof(buffer); // Convert buffer to float 62 | } 63 | 64 | void convertStringToIntArray(const char* str, int* intArray) { 65 | char buffer[20]; // Temporary buffer to hold each substring 66 | int index = 0; // Index for the integer array 67 | int bufferIndex = 0; // Index for the buffer 68 | 69 | for (int i = 0; str[i] != '\0'; i++) { 70 | if (str[i] == ',') { // When a comma is found 71 | buffer[bufferIndex] = '\0'; // Null-terminate the buffer string 72 | intArray[index] = atoi(buffer); // Convert buffer to int and store in array 73 | index++; // Move to the next position in integer array 74 | bufferIndex = 0; // Reset buffer index 75 | } else { 76 | buffer[bufferIndex++] = str[i]; // Copy characters to buffer 77 | } 78 | } 79 | 80 | // Don't forget to convert the last number in the string 81 | buffer[bufferIndex] = '\0'; // Null-terminate the buffer 82 | intArray[index] = atoi(buffer); // Convert buffer to int 83 | } 84 | -------------------------------------------------------------------------------- /fossa/103_lnurl.ino: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////// 2 | ///////////////LNURL STUFF////////////////// 3 | ////USING STEPAN SNIGREVS GREAT CRYTPO////// 4 | ////////////THANK YOU STEPAN//////////////// 5 | //////////////////////////////////////////// 6 | 7 | void makeLNURL() { 8 | int randomPin = random(1000, 9999); 9 | byte nonce[8]; 10 | for (int i = 0; i < 8; i++) { 11 | nonce[i] = random(256); 12 | } 13 | 14 | byte payload[51]; // 51 bytes is max one can get with xor-encryption 15 | 16 | size_t payload_len = xor_encrypt(payload, sizeof(payload), (uint8_t *)secretATM.c_str(), secretATM.length(), nonce, sizeof(nonce), randomPin, float(total)); 17 | String preparedURL = baseURLATM + "?atm=1&p="; 18 | preparedURL += toBase64(payload, payload_len, BASE64_URLSAFE | BASE64_NOPADDING); 19 | 20 | Serial.println(preparedURL); 21 | char Buf[200]; 22 | preparedURL.toCharArray(Buf, 200); 23 | char *url = Buf; 24 | byte *data = (byte *)calloc(strlen(url) * 2, sizeof(byte)); 25 | size_t len = 0; 26 | int res = convert_bits(data, &len, 5, (byte *)url, strlen(url), 8, 1); 27 | char *charLnurl = (char *)calloc(strlen(url) * 2, sizeof(byte)); 28 | bech32_encode(charLnurl, "lnurl", data, len); 29 | to_upper(charLnurl); 30 | qrData = baseURLATM.substring(0, baseURLATM.length() - 18) + "atm" + "?lightning=" + charLnurl; 31 | } 32 | 33 | int xor_encrypt(uint8_t *output, size_t outlen, uint8_t *key, size_t keylen, uint8_t *nonce, size_t nonce_len, uint64_t pin, uint64_t amount_in_cents) { 34 | // check we have space for all the data: 35 | // 36 | if (outlen < 2 + nonce_len + 1 + lenVarInt(pin) + 1 + lenVarInt(amount_in_cents) + 8) { 37 | return 0; 38 | } 39 | 40 | int cur = 0; 41 | output[cur] = 1; // variant: XOR encryption 42 | cur++; 43 | 44 | // nonce_len | nonce 45 | output[cur] = nonce_len; 46 | cur++; 47 | memcpy(output + cur, nonce, nonce_len); 48 | cur += nonce_len; 49 | 50 | // payload, unxored first - 51 | int payload_len = lenVarInt(pin) + 1 + lenVarInt(amount_in_cents); 52 | output[cur] = (uint8_t)payload_len; 53 | cur++; 54 | uint8_t *payload = output + cur; // pointer to the start of the payload 55 | cur += writeVarInt(pin, output + cur, outlen - cur); // pin code 56 | cur += writeVarInt(amount_in_cents, output + cur, outlen - cur); // amount 57 | cur++; 58 | 59 | // xor it with round key 60 | uint8_t hmacresult[32]; 61 | SHA256 h; 62 | h.beginHMAC(key, keylen); 63 | h.write((uint8_t *)"Round secret:", 13); 64 | h.write(nonce, nonce_len); 65 | h.endHMAC(hmacresult); 66 | for (int i = 0; i < payload_len; i++) { 67 | payload[i] = payload[i] ^ hmacresult[i]; 68 | } 69 | 70 | // add hmac to authenticate 71 | h.beginHMAC(key, keylen); 72 | h.write((uint8_t *)"Data:", 5); 73 | h.write(output, cur); 74 | h.endHMAC(hmacresult); 75 | memcpy(output + cur, hmacresult, 8); 76 | cur += 8; 77 | 78 | // return number of bytes written to the output 79 | return cur; 80 | } -------------------------------------------------------------------------------- /fossa/104_printer.ino: -------------------------------------------------------------------------------- 1 | #ifdef PRINTER 2 | 3 | // define a list of quote Strings that can be used to print on the receipt 4 | const char* quotes[13] = { 5 | "It's very attractive to the libertarian viewpoint if we can explain it properly. I'm better with code than with words though.\r\nSatoshi Nakamoto, Dec 14, 2008", 6 | "In a few decades when the reward gets too small, the transaction fee will become the main compensation for nodes.\r\nSatoshi Nakamoto, Feb 14, 2010", 7 | "I'm sure that in 20 years there will either be very large transaction volume or no volume.\r\nSatoshi Nakamoto, Feb 14, 2010", 8 | "Imagine if gold turned to lead when stolen. If the thief gives it back, it turns to gold again.\r\nSatoshi Nakamoto, Aug 11, 2010", 9 | "If you don't believe me or don't get it, I don't have time to try to convince you, sorry.\r\nSatoshi Nakamoto, Jul 29, 2010", 10 | "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks.", 11 | "It would have been nice to get this attention in any other context. WikiLeaks has kicked the hornet's nest, and the swarm is headed towards us.\r\nSatoshi Nakamoto, Dec 11, 2010", 12 | "Lost coins only make everyone else's coins worth slightly more. Think of it as a donation to everyone.\r\nSatoshi Nakamoto, Jul 21, 2010", 13 | "Sigh... why delete a wallet instead of moving it aside and keeping the old copy just in case? You should never delete a wallet.\r\nSatoshi Nakamoto, October 3rd, 2010", 14 | "It's very attractive to the libertarian viewpoint if we can explain it properly. I'm better with code than with words though.\r\nSatoshi Nakamoto, Nov 14 2008", 15 | "It's time we had the same thing for money. With e-currency based on cryptographic proof, without the need to trust a third party middleman, money can be secure and transactions effortless.\r\nSatoshi Nakamoto, Jan 11 2009", 16 | "Sorry to be a wet blanket. Writing a description for this thing for general audiences is bloody hard. There's nothing to relate it to.\r\nSatoshi Nakamoto, 05 Jul 2010", 17 | "Yes, but we can win a major battle in the arms race and gain a new territory of freedom for several years.\r\nSatoshi Nakamoto, 6 Nov 2008" 18 | }; 19 | 20 | // function that returns a random quote from the list of quotes 21 | const char* getRandomQuote() { 22 | return quotes[random(0, 13)]; 23 | } 24 | 25 | void printQRcode(String qrData, byte size = 2, bool isMainQR = true) { 26 | // Using a smaller size or adjusting based on isMainQR 27 | byte adjustedSize = isMainQR ? size : max(1, size - 10); // Ensure minimum size of 1 28 | 29 | // Adjust error correction: L (0x31), M (0x32), Q (0x33), H (0x34) 30 | byte eccLevel = 0x31; // Start with low and increase if errors persist 31 | 32 | // Commands setup 33 | const byte modelCommand[] = { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x43, adjustedSize }; 34 | const byte eccCommand[] = { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x45, eccLevel }; 35 | const byte printCommand[] = { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30 }; 36 | 37 | printerSerial.write(modelCommand, sizeof(modelCommand)); 38 | printerSerial.write(eccCommand, sizeof(eccCommand)); 39 | 40 | int len = qrData.length() + 3; 41 | if (len > 255) { 42 | // Simple error handling for data that is too large 43 | Serial.println("Data too long for a single QR code!"); 44 | return; 45 | } 46 | byte dataCommand[] = { 0x1D, 0x28, 0x6B, (byte)len, 0x00, 0x31, 0x50, 0x30 }; 47 | printerSerial.write(dataCommand, sizeof(dataCommand)); 48 | printerSerial.print(qrData); 49 | printerSerial.write(printCommand, sizeof(printCommand)); 50 | } 51 | 52 | void printReceipt() { 53 | printer.wake(); 54 | printer.setDefault(); 55 | printer.justify('C'); // Center align text 56 | printer.feed(3); 57 | printer.boldOn(); 58 | printer.setSize('L'); 59 | printer.println(thankYouT); 60 | printer.feed(1); 61 | printer.setSize('S'); 62 | printer.println(thisVoucherT + " " + String(float(total / 100)) + " " + currencyATM + " " + ofBitcoinT); 63 | printer.feed(1); 64 | printer.underlineOn(); 65 | printer.println(scanMeClaimT); 66 | printer.underlineOff(); 67 | printer.feed(1); 68 | printQRcode(qrData); 69 | printer.boldOff(); 70 | printer.feed(1); 71 | printer.justify('L'); 72 | printer.println(getRandomQuote()); 73 | printer.feed(3); 74 | printer.sleep(); 75 | } 76 | #endif 77 | -------------------------------------------------------------------------------- /fossa/105_display.ino: -------------------------------------------------------------------------------- 1 | void printMessage(String text1, String text2, String text3, int ftcolor, int bgcolor) { 2 | tft.fillScreen(bgcolor); 3 | tft.setTextColor(ftcolor, bgcolor); 4 | tft.setTextSize(4); 5 | tft.setCursor((480 - textWidth(text1, 4)) / 2, 40); 6 | tft.println(text1); 7 | tft.setCursor((480 - textWidth(text2, 4)) / 2, 120); 8 | tft.println(text2); 9 | tft.setTextSize(3); 10 | tft.setCursor((480 - textWidth(text3, 3)) / 2, 200); 11 | tft.println(text3); 12 | } 13 | 14 | void feedmefiat() { 15 | tft.setTextColor(TFT_WHITE); 16 | tft.setCursor((480 - textWidth(fossaT, 2)) / 2, 40); 17 | tft.setTextSize(2); 18 | tft.println(fossaT); 19 | tft.setCursor((480 - textWidth(feedT + " " + String(charge) + chargeT, 2)) / 2, 280); 20 | tft.setTextSize(2); 21 | tft.println(feedT + " " + String(charge) + chargeT); 22 | } 23 | 24 | void feedmefiatloop() { 25 | tft.setTextColor(homeScreenColors[homeScreenNumColorCount]); 26 | tft.setTextSize(8); 27 | tft.setCursor((480 - textWidth(satsT, 8)) / 2, 80); 28 | tft.println(satsT); 29 | tft.setCursor((480 - textWidth(forT, 8)) / 2, 140); 30 | tft.println(forT); 31 | tft.setCursor((480 - textWidth(fiatT, 8)) / 2, 200); 32 | tft.println(fiatT); 33 | delay(100); 34 | } 35 | 36 | void qrShowCodeLNURL(String message) { 37 | #ifdef PRINTER 38 | printMessage(printingT, waitT, "", TFT_WHITE, TFT_BLACK); 39 | delay(3000); 40 | printReceipt(); 41 | #endif 42 | Serial.println(qrData); 43 | tft.fillScreen(TFT_WHITE); 44 | const char *qrDataChar = qrData.c_str(); 45 | QRCode qrcoded; 46 | uint8_t qrcodeData[qrcode_getBufferSize(20)]; 47 | qrcode_initText(&qrcoded, qrcodeData, 11, 0, qrDataChar); 48 | 49 | for (uint8_t y = 0; y < qrcoded.size; y++) { 50 | for (uint8_t x = 0; x < qrcoded.size; x++) { 51 | if (qrcode_getModule(&qrcoded, x, y)) { 52 | tft.fillRect(120 + 4 * x, 20 + 4 * y, 4, 4, TFT_BLACK); 53 | } else { 54 | tft.fillRect(120 + 4 * x, 20 + 4 * y, 4, 4, TFT_WHITE); 55 | } 56 | } 57 | } 58 | 59 | tft.setCursor(40, 290); 60 | tft.setTextSize(2); 61 | tft.setTextColor(TFT_BLACK, TFT_WHITE); 62 | tft.println(message); 63 | delay(2000); 64 | waitForTap = true; 65 | while (waitForTap) { 66 | BTNA.read(); 67 | if (BTNA.wasReleased()) { 68 | waitForTap = false; 69 | } 70 | } 71 | } 72 | 73 | int textWidth(String text, int textSize) { 74 | tft.setTextSize(textSize); 75 | return tft.textWidth(text); 76 | } 77 | -------------------------------------------------------------------------------- /fossa/fossa.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "qrcoded.h" 10 | #include "Bitcoin.h" 11 | #include 12 | #include 13 | #define FORMAT_ON_FAIL true 14 | #define PARAM_FILE "/elements.json" 15 | 16 | #include 17 | 18 | //#define PRINTER 19 | #define BILL_ACCEPTOR 20 | #define COIN_ACCEPTOR 21 | 22 | #define BTN1 39 // Screen tap button 23 | #define BILL_RX 32 // RX Bill acceptor 24 | #define BILL_TX 33 // TX Bill acceptor 25 | #define COIN_TX 4 // TX Coinmech 26 | #define COIN_INHIBIT 2 // Coinmech 27 | #define PRINTER_RX 22 // RX of the thermal printer 28 | #define PRINTER_TX 23 // TX of the thermal printer 29 | 30 | // uncomment to use always hardcoded default settings 31 | #define HARDCODED 32 | 33 | // default settings 34 | #define LANGUAGE "en" // Supports en, es, fr, de, it, pt, pl, hu, tr, ro, fi, sv 35 | #define CHARGE 10 // % you will charge people for service, set in LNbits extension 36 | #define MAX_AMOUNT 30 // max amount per withdraw 37 | #define MAX_BEFORE_RESET 300 // max amount you want to sell in the atm before having to reset power 38 | #define DEVICE_STRING "https://XXXX.lnbits.com/fossa/api/v1/lnurl/XXXXX,XXXXXXXXXXXXXXXXXXXXXX,USD" 39 | 40 | int billAmountInt[16]; 41 | float coinAmountFloat[6]; 42 | int charge; 43 | int maxamount; 44 | int maxBeforeReset; 45 | String language; 46 | String deviceString; 47 | 48 | String baseURLATM; 49 | String secretATM; 50 | String currencyATM; 51 | 52 | fs::SPIFFSFS &FlashFS = SPIFFS; 53 | 54 | String qrData; 55 | 56 | int maxBeforeResetTally; 57 | int bills; 58 | float coins; 59 | float total; 60 | int billAmountSize = sizeof(billAmountInt) / sizeof(int); 61 | float coinAmountSize = sizeof(coinAmountFloat) / sizeof(float); 62 | int moneyTimer = 0; 63 | bool waitForTap = true; 64 | struct KeyValue { 65 | String key; 66 | String value; 67 | }; 68 | String translate(String key); 69 | uint16_t homeScreenColors[] = { TFT_GREEN, TFT_BLUE, TFT_ORANGE }; 70 | int homeScreenNumColors = sizeof(homeScreenColors) / sizeof(homeScreenColors[0]); 71 | int homeScreenNumColorCount = 0; 72 | 73 | String usbT, tapScreenT, scanMeT, totalT, fossaT, satsT, forT, fiatT, feedT, chargeT, printingT, waitT, workingT, thisVoucherT, ofBitcoinT, thankYouT, scanMeClaimT, tooMuchFiatT, contactOwnerT; 74 | 75 | #ifdef BILL_ACCEPTOR 76 | HardwareSerial SerialBillAcceptor(1); 77 | #endif 78 | #ifdef COIN_ACCEPTOR 79 | HardwareSerial SerialCoinAcceptor(2); 80 | #endif 81 | #ifdef PRINTER 82 | SoftwareSerial SerialPrinter(PRINTER_RX, PRINTER_TX); 83 | Adafruit_Thermal printer(&SerialPrinter); 84 | #endif 85 | 86 | TFT_eSPI tft = TFT_eSPI(TFT_WIDTH, TFT_HEIGHT); 87 | Button BTNA(BTN1); 88 | 89 | void setup() { 90 | Serial.begin(115200); 91 | Serial.println("TFT: " + String(TFT_WIDTH) + "x" + String(TFT_HEIGHT)); 92 | Serial.println("TFT pin MISO: " + String(TFT_MISO)); 93 | Serial.println("TFT pin CS: " + String(TFT_CS)); 94 | Serial.println("TFT pin MOSI: " + String(TFT_MOSI)); 95 | Serial.println("TFT pin SCLK: " + String(TFT_SCLK)); 96 | Serial.println("TFT pin DC: " + String(TFT_DC)); 97 | Serial.println("TFT pin RST: " + String(TFT_RST)); 98 | # ifdef HARDCODED 99 | setDefaultValues(); 100 | translateAll(language); 101 | # else 102 | Serial.println("Waiting for tap to start config mode.."); 103 | while (waitForTap && total < 100) { 104 | BTNA.read(); 105 | if (BTNA.wasReleased()) { 106 | Serial.println("Tap detected, starting config mode.."); 107 | printMessage(usbT, "", tapScreenT, TFT_WHITE, TFT_BLACK); 108 | executeConfig(); 109 | waitForTap = false; 110 | } 111 | delay(20); 112 | total++; 113 | } 114 | Serial.println("Config mode ended."); 115 | Serial.println("Reading files.."); 116 | readFiles(); 117 | translateAll(language); 118 | # endif 119 | BTNA.begin(); 120 | FlashFS.begin(FORMAT_ON_FAIL); 121 | tft.init(); 122 | tft.setRotation(1); 123 | tft.invertDisplay(false); 124 | printMessage("", "Loading..", "", TFT_WHITE, TFT_BLACK); 125 | splitSettings(deviceString); 126 | 127 | #ifdef BILL_ACCEPTOR 128 | SerialBillAcceptor.begin(300, SERIAL_8N2, BILL_TX, BILL_RX); 129 | Serial.println("Bill Acceptor serial started."); 130 | #endif 131 | #ifdef COIN_ACCEPTOR 132 | SerialCoinAcceptor.begin(4800, SERIAL_8N1, COIN_TX); 133 | pinMode(COIN_INHIBIT, OUTPUT); 134 | Serial.println("Coin Acceptor serial started."); 135 | #endif 136 | #ifdef PRINTER 137 | printerSerial.begin(9600); 138 | printer.begin(); 139 | #endif 140 | } 141 | 142 | void loop() { 143 | if (maxBeforeResetTally >= maxBeforeReset) { 144 | printMessage("", tooMuchFiatT, contactOwnerT, TFT_WHITE, TFT_BLACK); 145 | delay(100000000); 146 | } else { 147 | SerialBillAcceptor.write(184); 148 | digitalWrite(COIN_INHIBIT, HIGH); 149 | tft.fillScreen(TFT_BLACK); 150 | BTNA.read(); // needed to clear accidental taps 151 | moneyTimerFun(); 152 | Serial.println(total); 153 | Serial.println(maxBeforeResetTally); 154 | maxBeforeResetTally = maxBeforeResetTally + (total / 100); 155 | Serial.println(maxBeforeResetTally); 156 | makeLNURL(); 157 | qrShowCodeLNURL(scanMeT); 158 | } 159 | } 160 | 161 | void moneyTimerFun() { 162 | waitForTap = true; 163 | coins = 0; 164 | bills = 0; 165 | total = 0; 166 | while (waitForTap || total == 0) { 167 | if (homeScreenNumColorCount == homeScreenNumColors) { 168 | homeScreenNumColorCount = 0; 169 | } 170 | if (total == 0) { 171 | feedmefiat(); 172 | feedmefiatloop(); 173 | } 174 | #ifdef BILL_ACCEPTOR 175 | if (SerialBillAcceptor.available()) { 176 | int x = SerialBillAcceptor.read(); 177 | 178 | for (int i = 0; i < billAmountSize; i++) { 179 | if ((i + 1) == x) { 180 | bills = bills + billAmountInt[i]; 181 | total = (coins + bills); 182 | printMessage(billAmountInt[i] + currencyATM, totalT + String(total) + currencyATM, tapScreenT, TFT_WHITE, TFT_BLACK); 183 | } 184 | } 185 | } 186 | // Turn off 187 | SerialBillAcceptor.write(185); 188 | #endif 189 | #ifdef COIN_ACCEPTOR 190 | if (SerialCoinAcceptor.available()) { 191 | int x = SerialCoinAcceptor.read(); 192 | printMessage("", "WARNING: print bool", String(x), TFT_WHITE, TFT_BLACK); 193 | delay(3000); 194 | for (int i = 0; i < coinAmountSize; i++) { 195 | if ((i + 1) == x) { 196 | coins = coins + coinAmountFloat[i]; 197 | total = (coins + bills); 198 | printMessage(coinAmountFloat[i] + currencyATM, totalT + String(total) + currencyATM, tapScreenT, TFT_WHITE, TFT_BLACK); 199 | } 200 | } 201 | } 202 | // Turn off 203 | digitalWrite(COIN_INHIBIT, LOW); 204 | #endif 205 | BTNA.read(); 206 | if (BTNA.wasReleased() || total > maxamount) { 207 | waitForTap = false; 208 | } 209 | homeScreenNumColorCount++; 210 | } 211 | total = (coins + bills) * 100; 212 | } 213 | -------------------------------------------------------------------------------- /img/nv10-wiring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/img/nv10-wiring.png -------------------------------------------------------------------------------- /libraries/QRCode/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | This library is written and maintained by Richard Moore. 4 | Major parts were derived from Project Nayuki's library. 5 | 6 | Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) 7 | Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | 27 | -------------------------------------------------------------------------------- /libraries/QRCode/README.md: -------------------------------------------------------------------------------- 1 | # QRCode 2 | 3 | A simple library for generating [QR codes](https://en.wikipedia.org/wiki/QR_code) in C, 4 | optimized for processing and memory constrained systems. 5 | 6 | **Features:** 7 | 8 | - Stack-based (no heap necessary; but you can use heap if you want) 9 | - Low-memory foot print (relatively) 10 | - Compile-time stripping of unecessary logic and constants 11 | - MIT License; do with this as you please 12 | 13 | ## Installing 14 | 15 | To install this library, download and save it to your Arduino libraries directory. 16 | 17 | Rename the directory to QRCode (if downloaded from GitHub, the filename may be 18 | qrcoded-master; library names may not contain the hyphen, so it must be renamed) 19 | 20 | ## API 21 | 22 | **Generate a QR Code** 23 | 24 | ```c 25 | // The structure to manage the QR code 26 | QRCode qrcoded; 27 | 28 | // Allocate a chunk of memory to store the QR code 29 | uint8_t qrcodeBytes[qrcode_getBufferSize()]; 30 | 31 | qrcode_initText(&qrcoded, qrcodeBytes, 3, ECC_LOW, "HELLO WORLD"); 32 | ``` 33 | 34 | **Draw a QR Code** 35 | 36 | How a QR code is used will vary greatly from project to project. For example: 37 | 38 | - Display on an OLED screen (128x64 nicely supports 2 side-by-side version 3 QR codes) 39 | - Print as a bitmap on a thermal printer 40 | - Store as a BMP (or with a some extra work, possibly a PNG) on an SD card 41 | 42 | The following example prints a QR code to the Serial Monitor (it likely will 43 | not be scannable, but is just for demonstration purposes). 44 | 45 | ```c 46 | for (uint8 y = 0; y < qrcoded.size; y++) { 47 | for (uint8 x = 0; x < qrcoded.size; x++) { 48 | if (qrcode_getModule(&qrcoded, x, y) { 49 | Serial.print("**"); 50 | } else { 51 | Serial.print(" "); 52 | } 53 | } 54 | Serial.print("\n"); 55 | } 56 | ``` 57 | 58 | ## What is Version, Error Correction and Mode? 59 | 60 | A QR code is composed of many little squares, called **modules**, which represent 61 | encoded data, with additional error correction (allowing partially damaged QR 62 | codes to still be read). 63 | 64 | The **version** of a QR code is a number between 1 and 40 (inclusive), which indicates 65 | the size of the QR code. The width and height of a QR code are always equal (it is 66 | square) and are equal to `4 * version + 17`. 67 | 68 | The level of **error correction** is a number between 0 and 3 (inclusive), or can be 69 | one of the symbolic names ECC_LOW, ECC_MEDIUM, ECC_QUARTILE and ECC_HIGH. Higher 70 | levels of error correction sacrifice data capacity, but allow a larger portion of 71 | the QR code to be damaged or unreadable. 72 | 73 | The **mode** of a QR code is determined by the data being encoded. Each mode is encoded 74 | internally using a compact representation, so lower modes can contain more data. 75 | 76 | - **NUMERIC:** numbers (`0-9`) 77 | - **ALPHANUMERIC:** uppercase letters (`A-Z`), numbers (`0-9`), the space (` `), dollar sign (`$`), percent sign (`%`), asterisk (`*`), plus (`+`), minus (`-`), decimal point (`.`), slash (`/`) and colon (`:`). 78 | - **BYTE:** any character 79 | 80 | ## Data Capacities 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 |
VersionSizeError CorrectionMode
NumericAlphanumericByte
121 x 21LOW412517
MEDIUM342014
QUARTILE271611
HIGH17107
225 x 25LOW774732
MEDIUM633826
QUARTILE482920
HIGH342014
329 x 29LOW1277753
MEDIUM1016142
QUARTILE774732
HIGH583524
433 x 33LOW18711478
MEDIUM1499062
QUARTILE1116746
HIGH825034
537 x 37LOW255154106
MEDIUM20212284
QUARTILE1448760
HIGH1066444
641 x 41LOW322195134
MEDIUM255154106
QUARTILE17810874
HIGH1398458
745 x 45LOW370224154
MEDIUM293178122
QUARTILE20712586
HIGH1549364
849 x 49LOW461279192
MEDIUM365221152
QUARTILE259157108
HIGH20212284
953 x 53LOW552335230
MEDIUM432262180
QUARTILE312189130
HIGH23514398
1057 x 57LOW652395271
MEDIUM513311213
QUARTILE364221151
HIGH288174119
1161 x 61LOW772468321
MEDIUM604366251
QUARTILE427259177
HIGH331200137
1265 x 65LOW883535367
MEDIUM691419287
QUARTILE489296203
HIGH374227155
1369 x 69LOW1022619425
MEDIUM796483331
QUARTILE580352241
HIGH427259177
1473 x 73LOW1101667458
MEDIUM871528362
QUARTILE621376258
HIGH468283194
1577 x 77LOW1250758520
MEDIUM991600412
QUARTILE703426292
HIGH530321220
1681 x 81LOW1408854586
MEDIUM1082656450
QUARTILE775470322
HIGH602365250
1785 x 85LOW1548938644
MEDIUM1212734504
QUARTILE876531364
HIGH674408280
1889 x 89LOW17251046718
MEDIUM1346816560
QUARTILE948574394
HIGH746452310
1993 x 93LOW19031153792
MEDIUM1500909624
QUARTILE1063644442
HIGH813493338
2097 x 97LOW20611249858
MEDIUM1600970666
QUARTILE1159702482
HIGH919557382
21101 x 101LOW22321352929
MEDIUM17081035711
QUARTILE1224742509
HIGH969587403
22105 x 105LOW240914601003
MEDIUM18721134779
QUARTILE1358823565
HIGH1056640439
23109 x 109LOW262015881091
MEDIUM20591248857
QUARTILE1468890611
HIGH1108672461
24113 x 113LOW281217041171
MEDIUM21881326911
QUARTILE1588963661
HIGH1228744511
25117 x 117LOW305718531273
MEDIUM23951451997
QUARTILE17181041715
HIGH1286779535
26121 x 121LOW328319901367
MEDIUM254415421059
QUARTILE18041094751
HIGH1425864593
27125 x 125LOW351721321465
MEDIUM270116371125
QUARTILE19331172805
HIGH1501910625
28129 x 129LOW366922231528
MEDIUM285717321190
QUARTILE20851263868
HIGH1581958658
29133 x 133LOW390923691628
MEDIUM303518391264
QUARTILE21811322908
HIGH16771016698
30137 x 137LOW415825201732
MEDIUM328919941370
QUARTILE23581429982
HIGH17821080742
31141 x 141LOW441726771840
MEDIUM348621131452
QUARTILE247314991030
HIGH18971150790
32145 x 145LOW468628401952
MEDIUM369322381538
QUARTILE267016181112
HIGH20221226842
33149 x 149LOW496530092068
MEDIUM390923691628
QUARTILE280517001168
HIGH21571307898
34153 x 153LOW525331832188
MEDIUM413425061722
QUARTILE294917871228
HIGH23011394958
35157 x 157LOW552933512303
MEDIUM434326321809
QUARTILE308118671283
HIGH23611431983
36161 x 161LOW583635372431
MEDIUM458827801911
QUARTILE324419661351
HIGH252415301051
37165 x 165LOW615337292563
MEDIUM477528941989
QUARTILE341720711423
HIGH262515911093
38169 x 169LOW647939272699
MEDIUM503930542099
QUARTILE359921811499
HIGH273516581139
39173 x 173LOW674340872809
MEDIUM531332202213
QUARTILE379122981579
HIGH292717741219
40177 x 177LOW708942962953
MEDIUM559633912331
QUARTILE399324201663
HIGH305718521273
655 | 656 | ## Special Thanks 657 | 658 | A HUGE thank you to [Project Nayuki](https://www.nayuki.io/) for the 659 | [QR code C++ library](https://github.com/nayuki/QR-Code-generator/tree/master/cpp) 660 | which was critical in development of this library. 661 | 662 | ## License 663 | 664 | MIT License. 665 | -------------------------------------------------------------------------------- /libraries/QRCode/examples/QRCode/QRCode.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * QRCode 3 | * 4 | * A quick example of generating a QR code. 5 | * 6 | * This prints the QR code to the serial monitor as solid blocks. Each module 7 | * is two characters wide, since the monospace font used in the serial monitor 8 | * is approximately twice as tall as wide. 9 | * 10 | */ 11 | 12 | #include "qrcoded.h" 13 | 14 | void setup() 15 | { 16 | Serial.begin(115200); 17 | 18 | // Start time 19 | uint32_t dt = millis(); 20 | 21 | // Create the QR code 22 | QRCode qrcoded; 23 | uint8_t qrcodeData[qrcode_getBufferSize(3)]; 24 | qrcode_initText(&qrcoded, qrcodeData, 3, 0, "HELLO WORLD"); 25 | 26 | // Delta time 27 | dt = millis() - dt; 28 | Serial.print("QR Code Generation Time: "); 29 | Serial.print(dt); 30 | Serial.print("\n"); 31 | 32 | // Top quiet zone 33 | Serial.print("\n\n\n\n"); 34 | 35 | for (uint8_t y = 0; y < qrcoded.size; y++) 36 | { 37 | 38 | // Left quiet zone 39 | Serial.print(" "); 40 | 41 | // Each horizontal module 42 | for (uint8_t x = 0; x < qrcoded.size; x++) 43 | { 44 | 45 | // Print each module (UTF-8 \u2588 is a solid block) 46 | Serial.print(qrcode_getModule(&qrcoded, x, y) ? "\u2588\u2588" : " "); 47 | } 48 | 49 | Serial.print("\n"); 50 | } 51 | 52 | // Bottom quiet zone 53 | Serial.print("\n\n\n\n"); 54 | } 55 | 56 | void loop() 57 | { 58 | } 59 | -------------------------------------------------------------------------------- /libraries/QRCode/generate_table.py: -------------------------------------------------------------------------------- 1 | Data = [ 2 | ["1", "41", "25", "17", "34", "20", "14","27", "16", "11","17", "10", "7"], 3 | ["2", "77", "47", "32", "63", "38", "26", "48", "29", "20", "34", "20", "14"], 4 | ["3", "127", "77", "53", "101", "61", "42", "77", "47", "32", "58", "35", "24"], 5 | ["4", "187", "114", "78", "149", "90", "62", "111", "67", "46", "82", "50", "34"], 6 | ["5", "255", "154", "106", "202", "122", "84", "144", "87", "60", "106", "64", "44"], 7 | ["6", "322", "195", "134", "255", "154", "106", "178", "108", "74", "139", "84", "58"], 8 | ["7", "370", "224", "154", "293", "178", "122", "207", "125", "86", "154", "93", "64"], 9 | ["8", "461", "279", "192", "365", "221", "152", "259", "157", "108", "202", "122", "84"], 10 | ["9", "552", "335", "230", "432", "262", "180", "312", "189", "130", "235", "143", "98"], 11 | ["10", "652", "395", "271", "513", "311", "213", "364", "221", "151", "288", "174", "119"], 12 | ["11", "772", "468", "321", "604", "366", "251", "427", "259", "177", "331", "200", "137"], 13 | ["12", "883", "535", "367", "691", "419", "287", "489", "296", "203", "374", "227", "155"], 14 | ["13", "1022", "619", "425", "796", "483", "331", "580", "352", "241", "427", "259", "177"], 15 | ["14", "1101", "667", "458", "871", "528", "362", "621", "376", "258", "468", "283", "194"], 16 | ["15", "1250", "758", "520", "991", "600", "412", "703", "426", "292", "530", "321", "220"], 17 | ["16", "1408", "854", "586", "1082", "656", "450", "775", "470", "322", "602", "365", "250"], 18 | ["17", "1548", "938", "644", "1212", "734", "504", "876", "531", "364", "674", "408", "280"], 19 | ["18", "1725", "1046", "718", "1346", "816", "560", "948", "574", "394", "746", "452", "310"], 20 | ["19", "1903", "1153", "792", "1500", "909", "624", "1063", "644", "442", "813", "493", "338"], 21 | ["20", "2061", "1249", "858", "1600", "970", "666", "1159", "702", "482", "919", "557", "382"], 22 | ["21", "2232", "1352", "929", "1708", "1035", "711", "1224", "742", "509", "969", "587", "403"], 23 | ["22", "2409", "1460", "1003", "1872", "1134", "779", "1358", "823", "565", "1056", "640", "439"], 24 | ["23", "2620", "1588", "1091", "2059", "1248", "857", "1468", "890", "611", "1108", "672", "461"], 25 | ["24", "2812", "1704", "1171", "2188", "1326", "911", "1588", "963", "661", "1228", "744", "511"], 26 | ["25", "3057", "1853", "1273", "2395", "1451", "997", "1718", "1041", "715", "1286", "779", "535"], 27 | ["26", "3283", "1990", "1367", "2544", "1542", "1059", "1804", "1094", "751", "1425", "864", "593"], 28 | ["27", "3517", "2132", "1465", "2701", "1637", "1125", "1933", "1172", "805", "1501", "910", "625"], 29 | ["28", "3669", "2223", "1528", "2857", "1732", "1190", "2085", "1263", "868", "1581", "958", "658"], 30 | ["29", "3909", "2369", "1628", "3035", "1839", "1264", "2181", "1322", "908", "1677", "1016", "698"], 31 | ["30", "4158", "2520", "1732", "3289", "1994", "1370", "2358", "1429", "982", "1782", "1080", "742"], 32 | ["31", "4417", "2677", "1840", "3486", "2113", "1452", "2473", "1499", "1030", "1897", "1150", "790"], 33 | ["32", "4686", "2840", "1952", "3693", "2238", "1538", "2670", "1618", "1112", "2022", "1226", "842"], 34 | ["33", "4965", "3009", "2068", "3909", "2369", "1628", "2805", "1700", "1168", "2157", "1307", "898"], 35 | ["34", "5253", "3183", "2188", "4134", "2506", "1722", "2949", "1787", "1228", "2301", "1394", "958"], 36 | ["35", "5529", "3351", "2303", "4343", "2632", "1809", "3081", "1867", "1283", "2361", "1431", "983"], 37 | ["36", "5836", "3537", "2431", "4588", "2780", "1911", "3244", "1966", "1351", "2524", "1530", "1051"], 38 | ["37", "6153", "3729", "2563", "4775", "2894", "1989", "3417", "2071", "1423", "2625", "1591", "1093"], 39 | ["38", "6479", "3927", "2699", "5039", "3054", "2099", "3599", "2181", "1499", "2735", "1658", "1139"], 40 | ["39", "6743", "4087", "2809", "5313", "3220", "2213", "3791", "2298", "1579", "2927", "1774", "1219"], 41 | ["40", "7089", "4296", "2953", "5596", "3391", "2331", "3993", "2420", "1663", "3057", "1852", "1273"], 42 | ] 43 | Template = ''' 44 | %s 45 | %s 46 | LOW%s%s%s 47 | 48 | 49 | MEDIUM%s%s%s 50 | 51 | 52 | QUARTILE%s%s%s 53 | 54 | 55 | HIGH%s%s%s 56 | ''' 57 | 58 | for data in Data: 59 | data = data[:] 60 | size = 4 * int(data[0]) + 17 61 | data.insert(1, "%d x %d" % (size, size)) 62 | print Template % tuple(data) 63 | -------------------------------------------------------------------------------- /libraries/QRCode/keywords.txt: -------------------------------------------------------------------------------- 1 | 2 | # Datatypes (KEYWORD1) 3 | 4 | bool KEYWORD1 5 | uint8_t KEYWORD1 6 | QRCode KEYWORD1 7 | 8 | 9 | # Methods and Functions (KEYWORD2) 10 | 11 | qrcode_getBufferSize KEYWORD2 12 | qrcode_initText KEYWORD2 13 | qrcode_initBytes KEYWORD2 14 | qrcode_getModule KEYWORD2 15 | 16 | 17 | # Instances (KEYWORD2) 18 | 19 | 20 | # Constants (LITERAL1) 21 | 22 | false LITERAL1 23 | true LITERAL1 24 | 25 | ECC_LOW LITERAL1 26 | ECC_MEDIUM LITERAL1 27 | ECC_QUARTILE LITERAL1 28 | ECC_HIGH LITERAL1 29 | MODE_NUMERIC LITERAL1 30 | MODE_ALPHANUMERIC LITERAL1 31 | MODE_BYTE LITERAL1 32 | -------------------------------------------------------------------------------- /libraries/QRCode/library.properties: -------------------------------------------------------------------------------- 1 | name=QRCode 2 | version=0.0.1 3 | author=Richard Moore 4 | maintainer=Richard Moore 5 | sentence=A simple QR code generation library. 6 | paragraph=A simple QR code generation library. 7 | category=Other 8 | url=https://github.com/ricmoo/qrcoded/ 9 | architectures=* 10 | includes=qrcoded.h 11 | -------------------------------------------------------------------------------- /libraries/QRCode/src/qrcoded.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * This library is written and maintained by Richard Moore. 5 | * Major parts were derived from Project Nayuki's library. 6 | * 7 | * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) 8 | * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | */ 28 | 29 | /** 30 | * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was 31 | * heavily inspired and compared against. 32 | * 33 | * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp 34 | */ 35 | 36 | #ifndef __QRCODE_H_ 37 | #define __QRCODE_H_ 38 | 39 | #ifndef __cplusplus 40 | typedef unsigned char bool; 41 | static const bool false = 0; 42 | static const bool true = 1; 43 | #endif 44 | 45 | #include 46 | 47 | // QR Code Format Encoding 48 | #define MODE_NUMERIC 0 49 | #define MODE_ALPHANUMERIC 1 50 | #define MODE_BYTE 2 51 | 52 | // Error Correction Code Levels 53 | #define ECC_LOW 0 54 | #define ECC_MEDIUM 1 55 | #define ECC_QUARTILE 2 56 | #define ECC_HIGH 3 57 | 58 | // If set to non-zero, this library can ONLY produce QR codes at that version 59 | // This saves a lot of dynamic memory, as the codeword tables are skipped 60 | #ifndef LOCK_VERSION 61 | #define LOCK_VERSION 0 62 | #endif 63 | 64 | typedef struct QRCode 65 | { 66 | uint8_t version; 67 | uint8_t size; 68 | uint8_t ecc; 69 | uint8_t mode; 70 | uint8_t mask; 71 | uint8_t *modules; 72 | } QRCode; 73 | 74 | #ifdef __cplusplus 75 | extern "C" 76 | { 77 | #endif /* __cplusplus */ 78 | 79 | uint16_t qrcode_getBufferSize(uint8_t version); 80 | 81 | int8_t qrcode_initText(QRCode *qrcoded, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data); 82 | int8_t qrcode_initBytes(QRCode *qrcoded, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length); 83 | 84 | bool qrcode_getModule(QRCode *qrcoded, uint8_t x, uint8_t y); 85 | 86 | #ifdef __cplusplus 87 | } 88 | #endif /* __cplusplus */ 89 | 90 | #endif /* __QRCODE_H_ */ 91 | -------------------------------------------------------------------------------- /libraries/QRCode/tests/BitBuffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * QR Code generator library (C++) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/qr-code-generator-library 6 | * 7 | * (MIT License) 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | * this software and associated documentation files (the "Software"), to deal in 10 | * the Software without restriction, including without limitation the rights to 11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | * the Software, and to permit persons to whom the Software is furnished to do so, 13 | * subject to the following conditions: 14 | * - The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * - The Software is provided "as is", without warranty of any kind, express or 17 | * implied, including but not limited to the warranties of merchantability, 18 | * fitness for a particular purpose and noninfringement. In no event shall the 19 | * authors or copyright holders be liable for any claim, damages or other 20 | * liability, whether in an action of contract, tort or otherwise, arising from, 21 | * out of or in connection with the Software or the use or other dealings in the 22 | * Software. 23 | */ 24 | 25 | #include 26 | #include "BitBuffer.hpp" 27 | 28 | 29 | qrcodegen::BitBuffer::BitBuffer() : 30 | data(), 31 | bitLength(0) {} 32 | 33 | 34 | int qrcodegen::BitBuffer::getBitLength() const { 35 | return bitLength; 36 | } 37 | 38 | 39 | std::vector qrcodegen::BitBuffer::getBytes() const { 40 | return data; 41 | } 42 | 43 | 44 | void qrcodegen::BitBuffer::appendBits(uint32_t val, int len) { 45 | if (len < 0 || len > 32 || (len < 32 && (val >> len) != 0)) 46 | throw "Value out of range"; 47 | size_t newBitLen = bitLength + len; 48 | while (data.size() * 8 < newBitLen) 49 | data.push_back(0); 50 | for (int i = len - 1; i >= 0; i--, bitLength++) // Append bit by bit 51 | data.at(bitLength >> 3) |= ((val >> i) & 1) << (7 - (bitLength & 7)); 52 | } 53 | 54 | 55 | void qrcodegen::BitBuffer::appendData(const QrSegment &seg) { 56 | size_t newBitLen = bitLength + seg.bitLength; 57 | while (data.size() * 8 < newBitLen) 58 | data.push_back(0); 59 | for (int i = 0; i < seg.bitLength; i++, bitLength++) { // Append bit by bit 60 | int bit = (seg.data.at(i >> 3) >> (7 - (i & 7))) & 1; 61 | data.at(bitLength >> 3) |= bit << (7 - (bitLength & 7)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /libraries/QRCode/tests/BitBuffer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * QR Code generator library (C++) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/qr-code-generator-library 6 | * 7 | * (MIT License) 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | * this software and associated documentation files (the "Software"), to deal in 10 | * the Software without restriction, including without limitation the rights to 11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | * the Software, and to permit persons to whom the Software is furnished to do so, 13 | * subject to the following conditions: 14 | * - The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * - The Software is provided "as is", without warranty of any kind, express or 17 | * implied, including but not limited to the warranties of merchantability, 18 | * fitness for a particular purpose and noninfringement. In no event shall the 19 | * authors or copyright holders be liable for any claim, damages or other 20 | * liability, whether in an action of contract, tort or otherwise, arising from, 21 | * out of or in connection with the Software or the use or other dealings in the 22 | * Software. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | #include "QrSegment.hpp" 30 | 31 | 32 | namespace qrcodegen { 33 | 34 | /* 35 | * An appendable sequence of bits. Bits are packed in big endian within a byte. 36 | */ 37 | class BitBuffer { 38 | 39 | /*---- Fields ----*/ 40 | private: 41 | 42 | std::vector data; 43 | int bitLength; 44 | 45 | 46 | 47 | /*---- Constructor ----*/ 48 | public: 49 | 50 | // Creates an empty bit buffer (length 0). 51 | BitBuffer(); 52 | 53 | 54 | 55 | /*---- Methods ----*/ 56 | public: 57 | 58 | // Returns the number of bits in the buffer, which is a non-negative value. 59 | int getBitLength() const; 60 | 61 | 62 | // Returns a copy of all bytes, padding up to the nearest byte. 63 | std::vector getBytes() const; 64 | 65 | 66 | // Appends the given number of bits of the given value to this sequence. 67 | // If 0 <= len <= 31, then this requires 0 <= val < 2^len. 68 | void appendBits(uint32_t val, int len); 69 | 70 | 71 | // Appends the data of the given segment to this bit buffer. 72 | void appendData(const QrSegment &seg); 73 | 74 | }; 75 | 76 | } 77 | -------------------------------------------------------------------------------- /libraries/QRCode/tests/QrCode.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * QR Code generator library (C++) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/qr-code-generator-library 6 | * 7 | * (MIT License) 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | * this software and associated documentation files (the "Software"), to deal in 10 | * the Software without restriction, including without limitation the rights to 11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | * the Software, and to permit persons to whom the Software is furnished to do so, 13 | * subject to the following conditions: 14 | * - The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * - The Software is provided "as is", without warranty of any kind, express or 17 | * implied, including but not limited to the warranties of merchantability, 18 | * fitness for a particular purpose and noninfringement. In no event shall the 19 | * authors or copyright holders be liable for any claim, damages or other 20 | * liability, whether in an action of contract, tort or otherwise, arising from, 21 | * out of or in connection with the Software or the use or other dealings in the 22 | * Software. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "BitBuffer.hpp" 31 | #include "QrCode.hpp" 32 | 33 | 34 | qrcodegen::QrCode::Ecc::Ecc(int ord, int fb) : 35 | ordinal(ord), 36 | formatBits(fb) {} 37 | 38 | 39 | const qrcodegen::QrCode::Ecc qrcodegen::QrCode::Ecc::LOW (0, 1); 40 | const qrcodegen::QrCode::Ecc qrcodegen::QrCode::Ecc::MEDIUM (1, 0); 41 | const qrcodegen::QrCode::Ecc qrcodegen::QrCode::Ecc::QUARTILE(2, 3); 42 | const qrcodegen::QrCode::Ecc qrcodegen::QrCode::Ecc::HIGH (3, 2); 43 | 44 | 45 | qrcodegen::QrCode qrcodegen::QrCode::encodeText(const char *text, int version, const Ecc &ecl) { 46 | std::vector segs(QrSegment::makeSegments(text)); 47 | return encodeSegments(segs, ecl, version, version, -1, false); 48 | } 49 | 50 | 51 | qrcodegen::QrCode qrcodegen::QrCode::encodeBinary(const std::vector &data, const Ecc &ecl) { 52 | std::vector segs; 53 | segs.push_back(QrSegment::makeBytes(data)); 54 | return encodeSegments(segs, ecl); 55 | } 56 | 57 | 58 | qrcodegen::QrCode qrcodegen::QrCode::encodeSegments(const std::vector &segs, const Ecc &ecl, 59 | int minVersion, int maxVersion, int mask, bool boostEcl) { 60 | if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40) || mask < -1 || mask > 7) 61 | throw "Invalid value"; 62 | 63 | // Find the minimal version number to use 64 | int version, dataUsedBits; 65 | for (version = minVersion; ; version++) { 66 | int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available 67 | dataUsedBits = QrSegment::getTotalBits(segs, version); 68 | if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) 69 | break; // This version number is found to be suitable 70 | if (version >= maxVersion) // All versions in the range could not fit the given data 71 | throw "Data too long"; 72 | } 73 | if (dataUsedBits == -1) 74 | throw "Assertion error"; 75 | 76 | // Increase the error correction level while the data still fits in the current version number 77 | const Ecc *newEcl = &ecl; 78 | if (boostEcl) { 79 | if (dataUsedBits <= getNumDataCodewords(version, Ecc::MEDIUM ) * 8) newEcl = &Ecc::MEDIUM ; 80 | if (dataUsedBits <= getNumDataCodewords(version, Ecc::QUARTILE) * 8) newEcl = &Ecc::QUARTILE; 81 | if (dataUsedBits <= getNumDataCodewords(version, Ecc::HIGH ) * 8) newEcl = &Ecc::HIGH ; 82 | } 83 | 84 | // Create the data bit string by concatenating all segments 85 | int dataCapacityBits = getNumDataCodewords(version, *newEcl) * 8; 86 | BitBuffer bb; 87 | for (size_t i = 0; i < segs.size(); i++) { 88 | const QrSegment &seg(segs.at(i)); 89 | bb.appendBits(seg.mode.modeBits, 4); 90 | bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version)); 91 | bb.appendData(seg); 92 | } 93 | 94 | // Add terminator and pad up to a byte if applicable 95 | bb.appendBits(0, std::min(4, dataCapacityBits - bb.getBitLength())); 96 | bb.appendBits(0, (8 - bb.getBitLength() % 8) % 8); 97 | 98 | // Pad with alternate bytes until data capacity is reached 99 | for (uint8_t padByte = 0xEC; bb.getBitLength() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) 100 | bb.appendBits(padByte, 8); 101 | if (bb.getBitLength() % 8 != 0) 102 | throw "Assertion error"; 103 | 104 | // Create the QR Code symbol 105 | return QrCode(version, *newEcl, bb.getBytes(), mask); 106 | } 107 | 108 | 109 | qrcodegen::QrCode::QrCode(int ver, const Ecc &ecl, const std::vector &dataCodewords, int mask) : 110 | // Initialize scalar fields 111 | version(ver), 112 | size(1 <= ver && ver <= 40 ? ver * 4 + 17 : -1), // Avoid signed overflow undefined behavior 113 | errorCorrectionLevel(ecl) { 114 | 115 | // Check arguments 116 | if (ver < 1 || ver > 40 || mask < -1 || mask > 7) 117 | throw "Value out of range"; 118 | 119 | std::vector row(size); 120 | for (int i = 0; i < size; i++) { 121 | modules.push_back(row); 122 | isFunction.push_back(row); 123 | } 124 | 125 | // Draw function patterns, draw all codewords, do masking 126 | drawFunctionPatterns(); 127 | const std::vector allCodewords(appendErrorCorrection(dataCodewords)); 128 | drawCodewords(allCodewords); 129 | this->mask = handleConstructorMasking(mask); 130 | } 131 | 132 | 133 | qrcodegen::QrCode::QrCode(const QrCode &qr, int mask) : 134 | // Copy scalar fields 135 | version(qr.version), 136 | size(qr.size), 137 | errorCorrectionLevel(qr.errorCorrectionLevel) { 138 | 139 | // Check arguments 140 | if (mask < -1 || mask > 7) 141 | throw "Mask value out of range"; 142 | 143 | // Handle grid fields 144 | modules = qr.modules; 145 | isFunction = qr.isFunction; 146 | 147 | // Handle masking 148 | applyMask(qr.mask); // Undo old mask 149 | this->mask = handleConstructorMasking(mask); 150 | } 151 | 152 | 153 | int qrcodegen::QrCode::getMask() const { 154 | return mask; 155 | } 156 | 157 | 158 | int qrcodegen::QrCode::getModule(int x, int y) const { 159 | if (0 <= x && x < size && 0 <= y && y < size) 160 | return modules.at(y).at(x) ? 1 : 0; 161 | else 162 | return 0; // Infinite white border 163 | } 164 | 165 | 166 | std::string qrcodegen::QrCode::toSvgString(int border) const { 167 | if (border < 0) 168 | throw "Border must be non-negative"; 169 | std::ostringstream sb; 170 | sb << "\n"; 171 | sb << "\n"; 172 | sb << "\n"; 174 | sb << "\t\n"; 175 | sb << "\t\n"; 189 | sb << "\n"; 190 | return sb.str(); 191 | } 192 | 193 | 194 | void qrcodegen::QrCode::drawFunctionPatterns() { 195 | // Draw the horizontal and vertical timing patterns 196 | for (int i = 0; i < size; i++) { 197 | setFunctionModule(6, i, i % 2 == 0); 198 | setFunctionModule(i, 6, i % 2 == 0); 199 | } 200 | 201 | // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) 202 | drawFinderPattern(3, 3); 203 | drawFinderPattern(size - 4, 3); 204 | drawFinderPattern(3, size - 4); 205 | 206 | // Draw the numerous alignment patterns 207 | const std::vector alignPatPos(getAlignmentPatternPositions(version)); 208 | int numAlign = alignPatPos.size(); 209 | for (int i = 0; i < numAlign; i++) { 210 | for (int j = 0; j < numAlign; j++) { 211 | if ((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)) 212 | continue; // Skip the three finder corners 213 | else 214 | drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j)); 215 | } 216 | } 217 | 218 | // Draw configuration data 219 | drawFormatBits(0); // Dummy mask value; overwritten later in the constructor 220 | drawVersion(); 221 | } 222 | 223 | 224 | void qrcodegen::QrCode::drawFormatBits(int mask) { 225 | // Calculate error correction code and pack bits 226 | int data = errorCorrectionLevel.formatBits << 3 | mask; // errCorrLvl is uint2, mask is uint3 227 | int rem = data; 228 | for (int i = 0; i < 10; i++) 229 | rem = (rem << 1) ^ ((rem >> 9) * 0x537); 230 | data = data << 10 | rem; 231 | data ^= 0x5412; // uint15 232 | if (data >> 15 != 0) 233 | throw "Assertion error"; 234 | 235 | // Draw first copy 236 | for (int i = 0; i <= 5; i++) 237 | setFunctionModule(8, i, ((data >> i) & 1) != 0); 238 | setFunctionModule(8, 7, ((data >> 6) & 1) != 0); 239 | setFunctionModule(8, 8, ((data >> 7) & 1) != 0); 240 | setFunctionModule(7, 8, ((data >> 8) & 1) != 0); 241 | for (int i = 9; i < 15; i++) 242 | setFunctionModule(14 - i, 8, ((data >> i) & 1) != 0); 243 | 244 | // Draw second copy 245 | for (int i = 0; i <= 7; i++) 246 | setFunctionModule(size - 1 - i, 8, ((data >> i) & 1) != 0); 247 | for (int i = 8; i < 15; i++) 248 | setFunctionModule(8, size - 15 + i, ((data >> i) & 1) != 0); 249 | setFunctionModule(8, size - 8, true); 250 | } 251 | 252 | 253 | void qrcodegen::QrCode::drawVersion() { 254 | if (version < 7) 255 | return; 256 | 257 | // Calculate error correction code and pack bits 258 | int rem = version; // version is uint6, in the range [7, 40] 259 | for (int i = 0; i < 12; i++) 260 | rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); 261 | int data = version << 12 | rem; // uint18 262 | if (data >> 18 != 0) 263 | throw "Assertion error"; 264 | 265 | // Draw two copies 266 | for (int i = 0; i < 18; i++) { 267 | bool bit = ((data >> i) & 1) != 0; 268 | int a = size - 11 + i % 3, b = i / 3; 269 | setFunctionModule(a, b, bit); 270 | setFunctionModule(b, a, bit); 271 | } 272 | } 273 | 274 | 275 | void qrcodegen::QrCode::drawFinderPattern(int x, int y) { 276 | for (int i = -4; i <= 4; i++) { 277 | for (int j = -4; j <= 4; j++) { 278 | int dist = std::max(std::abs(i), std::abs(j)); // Chebyshev/infinity norm 279 | int xx = x + j, yy = y + i; 280 | if (0 <= xx && xx < size && 0 <= yy && yy < size) 281 | setFunctionModule(xx, yy, dist != 2 && dist != 4); 282 | } 283 | } 284 | } 285 | 286 | 287 | void qrcodegen::QrCode::drawAlignmentPattern(int x, int y) { 288 | for (int i = -2; i <= 2; i++) { 289 | for (int j = -2; j <= 2; j++) 290 | setFunctionModule(x + j, y + i, std::max(std::abs(i), std::abs(j)) != 1); 291 | } 292 | } 293 | 294 | 295 | void qrcodegen::QrCode::setFunctionModule(int x, int y, bool isBlack) { 296 | modules.at(y).at(x) = isBlack; 297 | isFunction.at(y).at(x) = true; 298 | } 299 | 300 | 301 | std::vector qrcodegen::QrCode::appendErrorCorrection(const std::vector &data) const { 302 | if (data.size() != static_cast(getNumDataCodewords(version, errorCorrectionLevel))) 303 | throw "Invalid argument"; 304 | 305 | // Calculate parameter numbers 306 | int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[errorCorrectionLevel.ordinal][version]; 307 | int totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[errorCorrectionLevel.ordinal][version]; 308 | if (totalEcc % numBlocks != 0) 309 | throw "Assertion error"; 310 | int blockEccLen = totalEcc / numBlocks; 311 | int numShortBlocks = numBlocks - getNumRawDataModules(version) / 8 % numBlocks; 312 | int shortBlockLen = getNumRawDataModules(version) / 8 / numBlocks; 313 | 314 | // Split data into blocks and append ECC to each block 315 | std::vector > blocks; 316 | const ReedSolomonGenerator rs(blockEccLen); 317 | for (int i = 0, k = 0; i < numBlocks; i++) { 318 | std::vector dat; 319 | dat.insert(dat.begin(), data.begin() + k, data.begin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))); 320 | k += dat.size(); 321 | const std::vector ecc(rs.getRemainder(dat)); 322 | if (i < numShortBlocks) 323 | dat.push_back(0); 324 | dat.insert(dat.end(), ecc.begin(), ecc.end()); 325 | blocks.push_back(dat); 326 | } 327 | 328 | // Interleave (not concatenate) the bytes from every block into a single sequence 329 | std::vector result; 330 | for (int i = 0; static_cast(i) < blocks.at(0).size(); i++) { 331 | for (int j = 0; static_cast(j) < blocks.size(); j++) { 332 | // Skip the padding byte in short blocks 333 | if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) 334 | result.push_back(blocks.at(j).at(i)); 335 | } 336 | } 337 | if (result.size() != static_cast(getNumRawDataModules(version) / 8)) 338 | throw "Assertion error"; 339 | return result; 340 | } 341 | 342 | 343 | void qrcodegen::QrCode::drawCodewords(const std::vector &data) { 344 | if (data.size() != static_cast(getNumRawDataModules(version) / 8)) 345 | throw "Invalid argument"; 346 | 347 | size_t i = 0; // Bit index into the data 348 | // Do the funny zigzag scan 349 | for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair 350 | if (right == 6) 351 | right = 5; 352 | for (int vert = 0; vert < size; vert++) { // Vertical counter 353 | for (int j = 0; j < 2; j++) { 354 | int x = right - j; // Actual x coordinate 355 | bool upwards = ((right & 2) == 0) ^ (x < 6); 356 | int y = upwards ? size - 1 - vert : vert; // Actual y coordinate 357 | if (!isFunction.at(y).at(x) && i < data.size() * 8) { 358 | modules.at(y).at(x) = ((data.at(i >> 3) >> (7 - (i & 7))) & 1) != 0; 359 | i++; 360 | } 361 | // If there are any remainder bits (0 to 7), they are already 362 | // set to 0/false/white when the grid of modules was initialized 363 | } 364 | } 365 | } 366 | if (static_cast(i) != data.size() * 8) 367 | throw "Assertion error"; 368 | } 369 | 370 | 371 | void qrcodegen::QrCode::applyMask(int mask) { 372 | if (mask < 0 || mask > 7) 373 | throw "Mask value out of range"; 374 | for (int y = 0; y < size; y++) { 375 | for (int x = 0; x < size; x++) { 376 | bool invert; 377 | switch (mask) { 378 | case 0: invert = (x + y) % 2 == 0; break; 379 | case 1: invert = y % 2 == 0; break; 380 | case 2: invert = x % 3 == 0; break; 381 | case 3: invert = (x + y) % 3 == 0; break; 382 | case 4: invert = (x / 3 + y / 2) % 2 == 0; break; 383 | case 5: invert = x * y % 2 + x * y % 3 == 0; break; 384 | case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; 385 | case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; 386 | default: throw "Assertion error"; 387 | } 388 | modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x)); 389 | } 390 | } 391 | } 392 | 393 | 394 | int qrcodegen::QrCode::handleConstructorMasking(int mask) { 395 | if (mask == -1) { // Automatically choose best mask 396 | int32_t minPenalty = INT32_MAX; 397 | for (int i = 0; i < 8; i++) { 398 | drawFormatBits(i); 399 | applyMask(i); 400 | int penalty = getPenaltyScore(); 401 | if (penalty < minPenalty) { 402 | mask = i; 403 | minPenalty = penalty; 404 | } 405 | applyMask(i); // Undoes the mask due to XOR 406 | } 407 | } 408 | if (mask < 0 || mask > 7) 409 | throw "Assertion error"; 410 | drawFormatBits(mask); // Overwrite old format bits 411 | applyMask(mask); // Apply the final choice of mask 412 | return mask; // The caller shall assign this value to the final-declared field 413 | } 414 | 415 | 416 | int qrcodegen::QrCode::getPenaltyScore() const { 417 | int result = 0; 418 | 419 | // Adjacent modules in row having same color 420 | for (int y = 0; y < size; y++) { 421 | bool colorX = modules.at(y).at(0); 422 | for (int x = 1, runX = 1; x < size; x++) { 423 | if (modules.at(y).at(x) != colorX) { 424 | colorX = modules.at(y).at(x); 425 | runX = 1; 426 | } else { 427 | runX++; 428 | if (runX == 5) 429 | result += PENALTY_N1; 430 | else if (runX > 5) 431 | result++; 432 | } 433 | } 434 | } 435 | // Adjacent modules in column having same color 436 | for (int x = 0; x < size; x++) { 437 | bool colorY = modules.at(0).at(x); 438 | for (int y = 1, runY = 1; y < size; y++) { 439 | if (modules.at(y).at(x) != colorY) { 440 | colorY = modules.at(y).at(x); 441 | runY = 1; 442 | } else { 443 | runY++; 444 | if (runY == 5) 445 | result += PENALTY_N1; 446 | else if (runY > 5) 447 | result++; 448 | } 449 | } 450 | } 451 | 452 | // 2*2 blocks of modules having same color 453 | for (int y = 0; y < size - 1; y++) { 454 | for (int x = 0; x < size - 1; x++) { 455 | bool color = modules.at(y).at(x); 456 | if ( color == modules.at(y).at(x + 1) && 457 | color == modules.at(y + 1).at(x) && 458 | color == modules.at(y + 1).at(x + 1)) 459 | result += PENALTY_N2; 460 | } 461 | } 462 | 463 | // Finder-like pattern in rows 464 | for (int y = 0; y < size; y++) { 465 | for (int x = 0, bits = 0; x < size; x++) { 466 | bits = ((bits << 1) & 0x7FF) | (modules.at(y).at(x) ? 1 : 0); 467 | if (x >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated 468 | result += PENALTY_N3; 469 | } 470 | } 471 | // Finder-like pattern in columns 472 | for (int x = 0; x < size; x++) { 473 | for (int y = 0, bits = 0; y < size; y++) { 474 | bits = ((bits << 1) & 0x7FF) | (modules.at(y).at(x) ? 1 : 0); 475 | if (y >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated 476 | result += PENALTY_N3; 477 | } 478 | } 479 | 480 | // Balance of black and white modules 481 | int black = 0; 482 | for (int y = 0; y < size; y++) { 483 | for (int x = 0; x < size; x++) { 484 | if (modules.at(y).at(x)) 485 | black++; 486 | } 487 | } 488 | int total = size * size; 489 | // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% 490 | for (int k = 0; black*20 < (9-k)*total || black*20 > (11+k)*total; k++) 491 | result += PENALTY_N4; 492 | return result; 493 | } 494 | 495 | 496 | std::vector qrcodegen::QrCode::getAlignmentPatternPositions(int ver) { 497 | if (ver < 1 || ver > 40) 498 | throw "Version number out of range"; 499 | else if (ver == 1) 500 | return std::vector(); 501 | else { 502 | int numAlign = ver / 7 + 2; 503 | int step; 504 | if (ver != 32) 505 | step = (ver * 4 + numAlign * 2 + 1) / (2 * numAlign - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2 506 | else // C-C-C-Combo breaker! 507 | step = 26; 508 | 509 | std::vector result; 510 | int size = ver * 4 + 17; 511 | for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step) 512 | result.insert(result.begin(), pos); 513 | result.insert(result.begin(), 6); 514 | return result; 515 | } 516 | } 517 | 518 | 519 | int qrcodegen::QrCode::getNumRawDataModules(int ver) { 520 | if (ver < 1 || ver > 40) 521 | throw "Version number out of range"; 522 | int result = (16 * ver + 128) * ver + 64; 523 | if (ver >= 2) { 524 | int numAlign = ver / 7 + 2; 525 | result -= (25 * numAlign - 10) * numAlign - 55; 526 | if (ver >= 7) 527 | result -= 18 * 2; // Subtract version information 528 | } 529 | return result; 530 | } 531 | 532 | 533 | int qrcodegen::QrCode::getNumDataCodewords(int ver, const Ecc &ecl) { 534 | if (ver < 1 || ver > 40) 535 | throw "Version number out of range"; 536 | return getNumRawDataModules(ver) / 8 - NUM_ERROR_CORRECTION_CODEWORDS[ecl.ordinal][ver]; 537 | } 538 | 539 | 540 | /*---- Tables of constants ----*/ 541 | 542 | const int qrcodegen::QrCode::PENALTY_N1 = 3; 543 | const int qrcodegen::QrCode::PENALTY_N2 = 3; 544 | const int qrcodegen::QrCode::PENALTY_N3 = 40; 545 | const int qrcodegen::QrCode::PENALTY_N4 = 10; 546 | 547 | 548 | const int16_t qrcodegen::QrCode::NUM_ERROR_CORRECTION_CODEWORDS[4][41] = { 549 | // Version: (note that index 0 is for padding, and is set to an illegal value) 550 | //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level 551 | {-1, 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low 552 | {-1, 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium 553 | {-1, 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile 554 | {-1, 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High 555 | }; 556 | 557 | const int8_t qrcodegen::QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { 558 | // Version: (note that index 0 is for padding, and is set to an illegal value) 559 | //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level 560 | {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low 561 | {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium 562 | {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile 563 | {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High 564 | }; 565 | 566 | 567 | qrcodegen::QrCode::ReedSolomonGenerator::ReedSolomonGenerator(int degree) : 568 | coefficients() { 569 | if (degree < 1 || degree > 255) 570 | throw "Degree out of range"; 571 | 572 | // Start with the monomial x^0 573 | coefficients.resize(degree); 574 | coefficients.at(degree - 1) = 1; 575 | 576 | // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), 577 | // drop the highest term, and store the rest of the coefficients in order of descending powers. 578 | // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). 579 | int root = 1; 580 | for (int i = 0; i < degree; i++) { 581 | // Multiply the current product by (x - r^i) 582 | for (size_t j = 0; j < coefficients.size(); j++) { 583 | coefficients.at(j) = multiply(coefficients.at(j), static_cast(root)); 584 | if (j + 1 < coefficients.size()) 585 | coefficients.at(j) ^= coefficients.at(j + 1); 586 | } 587 | root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) 588 | } 589 | } 590 | 591 | 592 | std::vector qrcodegen::QrCode::ReedSolomonGenerator::getRemainder(const std::vector &data) const { 593 | // Compute the remainder by performing polynomial division 594 | std::vector result(coefficients.size()); 595 | for (size_t i = 0; i < data.size(); i++) { 596 | uint8_t factor = data.at(i) ^ result.at(0); 597 | result.erase(result.begin()); 598 | result.push_back(0); 599 | for (size_t j = 0; j < result.size(); j++) 600 | result.at(j) ^= multiply(coefficients.at(j), factor); 601 | } 602 | return result; 603 | } 604 | 605 | 606 | uint8_t qrcodegen::QrCode::ReedSolomonGenerator::multiply(uint8_t x, uint8_t y) { 607 | // Russian peasant multiplication 608 | int z = 0; 609 | for (int i = 7; i >= 0; i--) { 610 | z = (z << 1) ^ ((z >> 7) * 0x11D); 611 | z ^= ((y >> i) & 1) * x; 612 | } 613 | if (z >> 8 != 0) 614 | throw "Assertion error"; 615 | return static_cast(z); 616 | } 617 | -------------------------------------------------------------------------------- /libraries/QRCode/tests/QrCode.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * QR Code generator library (C++) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/qr-code-generator-library 6 | * 7 | * (MIT License) 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | * this software and associated documentation files (the "Software"), to deal in 10 | * the Software without restriction, including without limitation the rights to 11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | * the Software, and to permit persons to whom the Software is furnished to do so, 13 | * subject to the following conditions: 14 | * - The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * - The Software is provided "as is", without warranty of any kind, express or 17 | * implied, including but not limited to the warranties of merchantability, 18 | * fitness for a particular purpose and noninfringement. In no event shall the 19 | * authors or copyright holders be liable for any claim, damages or other 20 | * liability, whether in an action of contract, tort or otherwise, arising from, 21 | * out of or in connection with the Software or the use or other dealings in the 22 | * Software. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | #include 30 | #include "QrSegment.hpp" 31 | 32 | 33 | namespace qrcodegen { 34 | 35 | /* 36 | * Represents an immutable square grid of black and white cells for a QR Code symbol, and 37 | * provides static functions to create a QR Code from user-supplied textual or binary data. 38 | * This class covers the QR Code model 2 specification, supporting all versions (sizes) 39 | * from 1 to 40, all 4 error correction levels, and only 3 character encoding modes. 40 | */ 41 | class QrCode { 42 | 43 | /*---- Public helper enumeration ----*/ 44 | public: 45 | 46 | /* 47 | * Represents the error correction level used in a QR Code symbol. 48 | */ 49 | class Ecc { 50 | // Constants declared in ascending order of error protection. 51 | public: 52 | const static Ecc LOW, MEDIUM, QUARTILE, HIGH; 53 | 54 | // Fields. 55 | public: 56 | const int ordinal; // (Public) In the range 0 to 3 (unsigned 2-bit integer). 57 | const int formatBits; // (Package-private) In the range 0 to 3 (unsigned 2-bit integer). 58 | 59 | // Constructor. 60 | private: 61 | Ecc(int ord, int fb); 62 | }; 63 | 64 | 65 | 66 | /*---- Public static factory functions ----*/ 67 | public: 68 | 69 | /* 70 | * Returns a QR Code symbol representing the given Unicode text string at the given error correction level. 71 | * As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer Unicode 72 | * code points (not UTF-16 code units). The smallest possible QR Code version is automatically chosen for the output. 73 | * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. 74 | */ 75 | static QrCode encodeText(const char *text, int version, const Ecc &ecl); 76 | 77 | 78 | /* 79 | * Returns a QR Code symbol representing the given binary data string at the given error correction level. 80 | * This function always encodes using the binary segment mode, not any text mode. The maximum number of 81 | * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. 82 | * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. 83 | */ 84 | static QrCode encodeBinary(const std::vector &data, const Ecc &ecl); 85 | 86 | 87 | /* 88 | * Returns a QR Code symbol representing the given data segments with the given encoding parameters. 89 | * The smallest possible QR Code version within the given range is automatically chosen for the output. 90 | * This function allows the user to create a custom sequence of segments that switches 91 | * between modes (such as alphanumeric and binary) to encode text more efficiently. 92 | * This function is considered to be lower level than simply encoding text or binary data. 93 | */ 94 | static QrCode encodeSegments(const std::vector &segs, const Ecc &ecl, 95 | int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters 96 | 97 | 98 | 99 | /*---- Instance fields ----*/ 100 | 101 | // Public immutable scalar parameters 102 | public: 103 | 104 | /* This QR Code symbol's version number, which is always between 1 and 40 (inclusive). */ 105 | const int version; 106 | 107 | /* The width and height of this QR Code symbol, measured in modules. 108 | * Always equal to version × 4 + 17, in the range 21 to 177. */ 109 | const int size; 110 | 111 | /* The error correction level used in this QR Code symbol. */ 112 | const Ecc &errorCorrectionLevel; 113 | 114 | /* The mask pattern used in this QR Code symbol, in the range 0 to 7 (i.e. unsigned 3-bit integer). 115 | * Note that even if a constructor was called with automatic masking requested 116 | * (mask = -1), the resulting object will still have a mask value between 0 and 7. */ 117 | private: 118 | int mask; 119 | 120 | // Private grids of modules/pixels (conceptually immutable) 121 | private: 122 | std::vector > modules; // The modules of this QR Code symbol (false = white, true = black) 123 | std::vector > isFunction; // Indicates function modules that are not subjected to masking 124 | 125 | 126 | 127 | /*---- Constructors ----*/ 128 | public: 129 | 130 | /* 131 | * Creates a new QR Code symbol with the given version number, error correction level, binary data array, 132 | * and mask number. This is a cumbersome low-level constructor that should not be invoked directly by the user. 133 | * To go one level up, see the encodeSegments() function. 134 | */ 135 | QrCode(int ver, const Ecc &ecl, const std::vector &dataCodewords, int mask); 136 | 137 | 138 | /* 139 | * Creates a new QR Code symbol based on the given existing object, but with a potentially 140 | * different mask pattern. The version, error correction level, codewords, etc. of the newly 141 | * created object are all identical to the argument object; only the mask may differ. 142 | */ 143 | QrCode(const QrCode &qr, int mask); 144 | 145 | 146 | 147 | /*---- Public instance methods ----*/ 148 | public: 149 | 150 | int getMask() const; 151 | 152 | 153 | /* 154 | * Returns the color of the module (pixel) at the given coordinates, which is either 0 for white or 1 for black. The top 155 | * left corner has the coordinates (x=0, y=0). If the given coordinates are out of bounds, then 0 (white) is returned. 156 | */ 157 | int getModule(int x, int y) const; 158 | 159 | 160 | /* 161 | * Based on the given number of border modules to add as padding, this returns a 162 | * string whose contents represents an SVG XML file that depicts this QR Code symbol. 163 | * Note that Unix newlines (\n) are always used, regardless of the platform. 164 | */ 165 | std::string toSvgString(int border) const; 166 | 167 | 168 | 169 | /*---- Private helper methods for constructor: Drawing function modules ----*/ 170 | private: 171 | 172 | void drawFunctionPatterns(); 173 | 174 | 175 | // Draws two copies of the format bits (with its own error correction code) 176 | // based on the given mask and this object's error correction level field. 177 | void drawFormatBits(int mask); 178 | 179 | 180 | // Draws two copies of the version bits (with its own error correction code), 181 | // based on this object's version field (which only has an effect for 7 <= version <= 40). 182 | void drawVersion(); 183 | 184 | 185 | // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). 186 | void drawFinderPattern(int x, int y); 187 | 188 | 189 | // Draws a 5*5 alignment pattern, with the center module at (x, y). 190 | void drawAlignmentPattern(int x, int y); 191 | 192 | 193 | // Sets the color of a module and marks it as a function module. 194 | // Only used by the constructor. Coordinates must be in range. 195 | void setFunctionModule(int x, int y, bool isBlack); 196 | 197 | 198 | /*---- Private helper methods for constructor: Codewords and masking ----*/ 199 | private: 200 | 201 | // Returns a new byte string representing the given data with the appropriate error correction 202 | // codewords appended to it, based on this object's version and error correction level. 203 | std::vector appendErrorCorrection(const std::vector &data) const; 204 | 205 | 206 | // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire 207 | // data area of this QR Code symbol. Function modules need to be marked off before this is called. 208 | void drawCodewords(const std::vector &data); 209 | 210 | 211 | // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical 212 | // properties, calling applyMask(m) twice with the same value is equivalent to no change at all. 213 | // This means it is possible to apply a mask, undo it, and try another mask. Note that a final 214 | // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). 215 | void applyMask(int mask); 216 | 217 | 218 | // A messy helper function for the constructors. This QR Code must be in an unmasked state when this 219 | // method is called. The given argument is the requested mask, which is -1 for auto or 0 to 7 for fixed. 220 | // This method applies and returns the actual mask chosen, from 0 to 7. 221 | int handleConstructorMasking(int mask); 222 | 223 | 224 | // Calculates and returns the penalty score based on state of this QR Code's current modules. 225 | // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. 226 | int getPenaltyScore() const; 227 | 228 | 229 | 230 | /*---- Private static helper functions ----*/ 231 | private: 232 | 233 | // Returns a set of positions of the alignment patterns in ascending order. These positions are 234 | // used on both the x and y axes. Each value in the resulting array is in the range [0, 177). 235 | // This stateless pure function could be implemented as table of 40 variable-length lists of unsigned bytes. 236 | static std::vector getAlignmentPatternPositions(int ver); 237 | 238 | 239 | // Returns the number of raw data modules (bits) available at the given version number. 240 | // These data modules are used for both user data codewords and error correction codewords. 241 | // This stateless pure function could be implemented as a 40-entry lookup table. 242 | static int getNumRawDataModules(int ver); 243 | 244 | 245 | // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any 246 | // QR Code of the given version number and error correction level, with remainder bits discarded. 247 | // This stateless pure function could be implemented as a (40*4)-cell lookup table. 248 | static int getNumDataCodewords(int ver, const Ecc &ecl); 249 | 250 | 251 | /*---- Private tables of constants ----*/ 252 | private: 253 | 254 | // For use in getPenaltyScore(), when evaluating which mask is best. 255 | static const int PENALTY_N1; 256 | static const int PENALTY_N2; 257 | static const int PENALTY_N3; 258 | static const int PENALTY_N4; 259 | 260 | static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4][41]; 261 | static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; 262 | 263 | 264 | 265 | /*---- Private helper class ----*/ 266 | private: 267 | 268 | /* 269 | * Computes the Reed-Solomon error correction codewords for a sequence of data codewords 270 | * at a given degree. Objects are immutable, and the state only depends on the degree. 271 | * This class exists because the divisor polynomial does not need to be recalculated for every input. 272 | */ 273 | class ReedSolomonGenerator { 274 | 275 | /*-- Immutable field --*/ 276 | private: 277 | 278 | // Coefficients of the divisor polynomial, stored from highest to lowest power, excluding the leading term which 279 | // is always 1. For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. 280 | std::vector coefficients; 281 | 282 | 283 | /*-- Constructor --*/ 284 | public: 285 | 286 | /* 287 | * Creates a Reed-Solomon ECC generator for the given degree. This could be implemented 288 | * as a lookup table over all possible parameter values, instead of as an algorithm. 289 | */ 290 | ReedSolomonGenerator(int degree); 291 | 292 | 293 | /*-- Method --*/ 294 | public: 295 | 296 | /* 297 | * Computes and returns the Reed-Solomon error correction codewords for the given sequence of data codewords. 298 | * The returned object is always a new byte array. This method does not alter this object's state (because it is immutable). 299 | */ 300 | std::vector getRemainder(const std::vector &data) const; 301 | 302 | 303 | /*-- Static function --*/ 304 | private: 305 | 306 | // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result 307 | // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. 308 | static uint8_t multiply(uint8_t x, uint8_t y); 309 | 310 | }; 311 | 312 | }; 313 | 314 | } 315 | -------------------------------------------------------------------------------- /libraries/QRCode/tests/QrSegment.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * QR Code generator library (C++) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/qr-code-generator-library 6 | * 7 | * (MIT License) 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | * this software and associated documentation files (the "Software"), to deal in 10 | * the Software without restriction, including without limitation the rights to 11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | * the Software, and to permit persons to whom the Software is furnished to do so, 13 | * subject to the following conditions: 14 | * - The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * - The Software is provided "as is", without warranty of any kind, express or 17 | * implied, including but not limited to the warranties of merchantability, 18 | * fitness for a particular purpose and noninfringement. In no event shall the 19 | * authors or copyright holders be liable for any claim, damages or other 20 | * liability, whether in an action of contract, tort or otherwise, arising from, 21 | * out of or in connection with the Software or the use or other dealings in the 22 | * Software. 23 | */ 24 | 25 | #include 26 | #include "BitBuffer.hpp" 27 | #include "QrSegment.hpp" 28 | 29 | 30 | qrcodegen::QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) : 31 | modeBits(mode) { 32 | numBitsCharCount[0] = cc0; 33 | numBitsCharCount[1] = cc1; 34 | numBitsCharCount[2] = cc2; 35 | } 36 | 37 | 38 | int qrcodegen::QrSegment::Mode::numCharCountBits(int ver) const { 39 | if ( 1 <= ver && ver <= 9) return numBitsCharCount[0]; 40 | else if (10 <= ver && ver <= 26) return numBitsCharCount[1]; 41 | else if (27 <= ver && ver <= 40) return numBitsCharCount[2]; 42 | else throw "Version number out of range"; 43 | } 44 | 45 | 46 | const qrcodegen::QrSegment::Mode qrcodegen::QrSegment::Mode::NUMERIC (0x1, 10, 12, 14); 47 | const qrcodegen::QrSegment::Mode qrcodegen::QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13); 48 | const qrcodegen::QrSegment::Mode qrcodegen::QrSegment::Mode::BYTE (0x4, 8, 16, 16); 49 | const qrcodegen::QrSegment::Mode qrcodegen::QrSegment::Mode::KANJI (0x8, 8, 10, 12); 50 | 51 | 52 | 53 | qrcodegen::QrSegment qrcodegen::QrSegment::makeBytes(const std::vector &data) { 54 | return QrSegment(Mode::BYTE, data.size(), data, data.size() * 8); 55 | } 56 | 57 | 58 | qrcodegen::QrSegment qrcodegen::QrSegment::makeNumeric(const char *digits) { 59 | BitBuffer bb; 60 | int accumData = 0; 61 | int accumCount = 0; 62 | int charCount = 0; 63 | for (; *digits != '\0'; digits++, charCount++) { 64 | char c = *digits; 65 | if (c < '0' || c > '9') 66 | throw "String contains non-numeric characters"; 67 | accumData = accumData * 10 + (c - '0'); 68 | accumCount++; 69 | if (accumCount == 3) { 70 | bb.appendBits(accumData, 10); 71 | accumData = 0; 72 | accumCount = 0; 73 | } 74 | } 75 | if (accumCount > 0) // 1 or 2 digits remaining 76 | bb.appendBits(accumData, accumCount * 3 + 1); 77 | return QrSegment(Mode::NUMERIC, charCount, bb.getBytes(), bb.getBitLength()); 78 | } 79 | 80 | 81 | qrcodegen::QrSegment qrcodegen::QrSegment::makeAlphanumeric(const char *text) { 82 | BitBuffer bb; 83 | int accumData = 0; 84 | int accumCount = 0; 85 | int charCount = 0; 86 | for (; *text != '\0'; text++, charCount++) { 87 | char c = *text; 88 | if (c < ' ' || c > 'Z') 89 | throw "String contains unencodable characters in alphanumeric mode"; 90 | accumData = accumData * 45 + ALPHANUMERIC_ENCODING_TABLE[c - ' ']; 91 | accumCount++; 92 | if (accumCount == 2) { 93 | bb.appendBits(accumData, 11); 94 | accumData = 0; 95 | accumCount = 0; 96 | } 97 | } 98 | if (accumCount > 0) // 1 character remaining 99 | bb.appendBits(accumData, 6); 100 | return QrSegment(Mode::ALPHANUMERIC, charCount, bb.getBytes(), bb.getBitLength()); 101 | } 102 | 103 | 104 | std::vector qrcodegen::QrSegment::makeSegments(const char *text) { 105 | // Select the most efficient segment encoding automatically 106 | std::vector result; 107 | if (*text == '\0'); // Leave the vector empty 108 | else if (QrSegment::isNumeric(text)) 109 | result.push_back(QrSegment::makeNumeric(text)); 110 | else if (QrSegment::isAlphanumeric(text)) 111 | result.push_back(QrSegment::makeAlphanumeric(text)); 112 | else { 113 | std::vector bytes; 114 | for (; *text != '\0'; text++) 115 | bytes.push_back(static_cast(*text)); 116 | result.push_back(QrSegment::makeBytes(bytes)); 117 | } 118 | return result; 119 | } 120 | 121 | 122 | qrcodegen::QrSegment::QrSegment(const Mode &md, int numCh, const std::vector &b, int bitLen) : 123 | mode(md), 124 | numChars(numCh), 125 | data(b), 126 | bitLength(bitLen) { 127 | if (numCh < 0 || bitLen < 0 || b.size() != static_cast((bitLen + 7) / 8)) 128 | throw "Invalid value"; 129 | } 130 | 131 | 132 | int qrcodegen::QrSegment::getTotalBits(const std::vector &segs, int version) { 133 | if (version < 1 || version > 40) 134 | throw "Version number out of range"; 135 | int result = 0; 136 | for (size_t i = 0; i < segs.size(); i++) { 137 | const QrSegment &seg(segs.at(i)); 138 | int ccbits = seg.mode.numCharCountBits(version); 139 | // Fail if segment length value doesn't fit in the length field's bit-width 140 | if (seg.numChars >= (1 << ccbits)) 141 | return -1; 142 | result += 4 + ccbits + seg.bitLength; 143 | } 144 | return result; 145 | } 146 | 147 | 148 | bool qrcodegen::QrSegment::isAlphanumeric(const char *text) { 149 | for (; *text != '\0'; text++) { 150 | char c = *text; 151 | if (c < ' ' || c > 'Z' || ALPHANUMERIC_ENCODING_TABLE[c - ' '] == -1) 152 | return false; 153 | } 154 | return true; 155 | } 156 | 157 | 158 | bool qrcodegen::QrSegment::isNumeric(const char *text) { 159 | for (; *text != '\0'; text++) { 160 | char c = *text; 161 | if (c < '0' || c > '9') 162 | return false; 163 | } 164 | return true; 165 | } 166 | 167 | 168 | const int8_t qrcodegen::QrSegment::ALPHANUMERIC_ENCODING_TABLE[59] = { 169 | // SP, !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <, =, >, ?, @, // ASCII codes 32 to 64 170 | 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, -1, // Array indices 0 to 32 171 | 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, // Array indices 33 to 58 172 | // A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, // ASCII codes 65 to 90 173 | }; 174 | -------------------------------------------------------------------------------- /libraries/QRCode/tests/QrSegment.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * QR Code generator library (C++) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/qr-code-generator-library 6 | * 7 | * (MIT License) 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | * this software and associated documentation files (the "Software"), to deal in 10 | * the Software without restriction, including without limitation the rights to 11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | * the Software, and to permit persons to whom the Software is furnished to do so, 13 | * subject to the following conditions: 14 | * - The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * - The Software is provided "as is", without warranty of any kind, express or 17 | * implied, including but not limited to the warranties of merchantability, 18 | * fitness for a particular purpose and noninfringement. In no event shall the 19 | * authors or copyright holders be liable for any claim, damages or other 20 | * liability, whether in an action of contract, tort or otherwise, arising from, 21 | * out of or in connection with the Software or the use or other dealings in the 22 | * Software. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | 30 | 31 | namespace qrcodegen { 32 | 33 | /* 34 | * Represents a character string to be encoded in a QR Code symbol. Each segment has 35 | * a mode, and a sequence of characters that is already encoded as a sequence of bits. 36 | * Instances of this class are immutable. 37 | * This segment class imposes no length restrictions, but QR Codes have restrictions. 38 | * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. 39 | * Any segment longer than this is meaningless for the purpose of generating QR Codes. 40 | */ 41 | class QrSegment { 42 | 43 | /*---- Public helper enumeration ----*/ 44 | 45 | /* 46 | * The mode field of a segment. Immutable. Provides methods to retrieve closely related values. 47 | */ 48 | public: 49 | class Mode { 50 | 51 | /*-- Constants --*/ 52 | public: 53 | 54 | static const Mode NUMERIC; 55 | static const Mode ALPHANUMERIC; 56 | static const Mode BYTE; 57 | static const Mode KANJI; 58 | 59 | 60 | /*-- Fields --*/ 61 | 62 | /* (Package-private) An unsigned 4-bit integer value (range 0 to 15) representing the mode indicator bits for this mode object. */ 63 | public: 64 | const int modeBits; 65 | 66 | private: 67 | int numBitsCharCount[3]; 68 | 69 | 70 | /*-- Constructor --*/ 71 | 72 | private: 73 | Mode(int mode, int cc0, int cc1, int cc2); 74 | 75 | 76 | /*-- Method --*/ 77 | 78 | /* 79 | * (Package-private) Returns the bit width of the segment character count field for this mode object at the given version number. 80 | */ 81 | public: 82 | int numCharCountBits(int ver) const; 83 | 84 | }; 85 | 86 | 87 | 88 | /*---- Public static factory functions ----*/ 89 | public: 90 | 91 | /* 92 | * Returns a segment representing the given binary data encoded in byte mode. 93 | */ 94 | static QrSegment makeBytes(const std::vector &data); 95 | 96 | 97 | /* 98 | * Returns a segment representing the given string of decimal digits encoded in numeric mode. 99 | */ 100 | static QrSegment makeNumeric(const char *digits); 101 | 102 | 103 | /* 104 | * Returns a segment representing the given text string encoded in alphanumeric mode. The characters allowed are: 105 | * 0 to 9, A to Z (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. 106 | */ 107 | static QrSegment makeAlphanumeric(const char *text); 108 | 109 | 110 | /* 111 | * Returns a list of zero or more segments to represent the given text string. 112 | * The result may use various segment modes and switch modes to optimize the length of the bit stream. 113 | */ 114 | static std::vector makeSegments(const char *text); 115 | 116 | 117 | /*---- Public static helper functions ----*/ 118 | public: 119 | 120 | /* 121 | * Tests whether the given string can be encoded as a segment in alphanumeric mode. 122 | */ 123 | static bool isAlphanumeric(const char *text); 124 | 125 | 126 | /* 127 | * Tests whether the given string can be encoded as a segment in numeric mode. 128 | */ 129 | static bool isNumeric(const char *text); 130 | 131 | 132 | 133 | /*---- Instance fields ----*/ 134 | public: 135 | 136 | /* The mode indicator for this segment. */ 137 | const Mode mode; 138 | 139 | /* The length of this segment's unencoded data, measured in characters. Always zero or positive. */ 140 | const int numChars; 141 | 142 | /* The bits of this segment packed into a byte array in big endian. */ 143 | const std::vector data; 144 | 145 | /* The length of this segment's encoded data, measured in bits. Satisfies ceil(bitLength / 8) = data.size(). */ 146 | const int bitLength; 147 | 148 | 149 | /*---- Constructor ----*/ 150 | public: 151 | 152 | /* 153 | * Creates a new QR Code data segment with the given parameters and data. 154 | */ 155 | QrSegment(const Mode &md, int numCh, const std::vector &b, int bitLen); 156 | 157 | 158 | // Package-private helper function. 159 | static int getTotalBits(const std::vector &segs, int version); 160 | 161 | 162 | /*---- Private constant ----*/ 163 | private: 164 | 165 | /* Maps shifted ASCII codes to alphanumeric mode character codes. */ 166 | static const int8_t ALPHANUMERIC_ENCODING_TABLE[59]; 167 | 168 | }; 169 | 170 | } 171 | -------------------------------------------------------------------------------- /libraries/QRCode/tests/README.md: -------------------------------------------------------------------------------- 1 | Testing 2 | ======= 3 | 4 | The testcases work by using the Nayuki QR code generating library, generating a QR code 5 | in both libraries and comparing them. 6 | 7 | Running 8 | ------- 9 | 10 | ``` 11 | ./run.sh 12 | ``` 13 | -------------------------------------------------------------------------------- /libraries/QRCode/tests/run-tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../src/qrcoded.h" 5 | #include "QrCode.hpp" 6 | 7 | static uint32_t check(const qrcodegen::QrCode &nayuki, QRCode *ricmoo) 8 | { 9 | uint32_t wrong = 0; 10 | 11 | if (nayuki.size != ricmoo->size) 12 | { 13 | wrong += (1 << 20); 14 | } 15 | 16 | int border = 4; 17 | for (int y = -border; y < nayuki.size + border; y++) 18 | { 19 | for (int x = -border; x < nayuki.size + border; x++) 20 | { 21 | if (!!nayuki.getModule(x, y) != qrcode_getModule(ricmoo, x, y)) 22 | { 23 | wrong++; 24 | } 25 | } 26 | } 27 | 28 | return wrong; 29 | } 30 | 31 | int main() 32 | { 33 | std::clock_t t0, totalNayuki, totalRicMoo; 34 | 35 | int total = 0, passed = 0; 36 | for (char version = 1; version <= 40; version++) 37 | { 38 | if (LOCK_VERSION != 0 && LOCK_VERSION != version) 39 | { 40 | continue; 41 | } 42 | 43 | for (char ecc = 0; ecc < 4; ecc++) 44 | { 45 | const qrcodegen::QrCode::Ecc *errCorLvl; 46 | switch (ecc) 47 | { 48 | case 0: 49 | errCorLvl = &qrcodegen::QrCode::Ecc::LOW; 50 | break; 51 | case 1: 52 | errCorLvl = &qrcodegen::QrCode::Ecc::MEDIUM; 53 | break; 54 | case 2: 55 | errCorLvl = &qrcodegen::QrCode::Ecc::QUARTILE; 56 | break; 57 | case 3: 58 | errCorLvl = &qrcodegen::QrCode::Ecc::HIGH; 59 | break; 60 | } 61 | 62 | for (char tc = 0; tc < 3; tc++) 63 | { 64 | char *data; 65 | switch (tc) 66 | { 67 | case 0: 68 | data = (char *)"HELLO"; 69 | break; 70 | case 1: 71 | data = (char *)"Hello"; 72 | break; 73 | case 2: 74 | data = (char *)"1234"; 75 | break; 76 | } 77 | t0 = std::clock(); 78 | const qrcodegen::QrCode nayuki = qrcodegen::QrCode::encodeText(data, version, *errCorLvl); 79 | totalNayuki += std::clock() - t0; 80 | 81 | t0 = std::clock(); 82 | QRCode ricmoo; 83 | uint8_t ricmooBytes[qrcode_getBufferSize(version)]; 84 | qrcode_initText(&ricmoo, ricmooBytes, version, ecc, data); 85 | totalRicMoo += std::clock() - t0; 86 | 87 | uint32_t badModules = check(nayuki, &ricmoo); 88 | if (badModules) 89 | { 90 | printf("Failed test case: version=%d, ecc=%d, data=\"%s\", faliured=%d\n", version, ecc, data, badModules); 91 | } 92 | else 93 | { 94 | passed++; 95 | } 96 | 97 | total++; 98 | } 99 | } 100 | } 101 | 102 | printf("Tests complete: %d passed (out of %d)\n", passed, total); 103 | printf("Timing: Nayuki=%lu, RicMoo=%lu\n", totalNayuki, totalRicMoo); 104 | } 105 | -------------------------------------------------------------------------------- /libraries/QRCode/tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | clang++ run-tests.cpp QrCode.cpp QrSegment.cpp BitBuffer.cpp ../src/qrcoded.c -o test && ./test 4 | clang++ run-tests.cpp QrCode.cpp QrSegment.cpp BitBuffer.cpp ../src/qrcoded.c -o test -D LOCK_VERSION=3 && ./test 5 | 6 | -------------------------------------------------------------------------------- /stls/WT32-SC01 enclosure lower.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/stls/WT32-SC01 enclosure lower.stl -------------------------------------------------------------------------------- /stls/WT32-SC01 enclosure upper.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lnbits/fossa/f9ce305729336882b59fccef895511b250b99072/stls/WT32-SC01 enclosure upper.stl -------------------------------------------------------------------------------- /tft_config.txt: -------------------------------------------------------------------------------- 1 | ST7796_DRIVER=1 2 | TFT_WIDTH=480 3 | TFT_HEIGHT=320 4 | TFT_BACKLIGHT_ON=HIGH 5 | USE_HSPI_PORT=1 6 | TFT_MISO=12 7 | TFT_MOSI=13 8 | TFT_SCLK=14 9 | TFT_CS=15 10 | TFT_DC=21 11 | TFT_RST=22 12 | TFT_BL=23 13 | -------------------------------------------------------------------------------- /tft_config_IL9341.txt: -------------------------------------------------------------------------------- 1 | ILI9341_DRIVER=1 2 | TFT_MISO=19 3 | TFT_MOSI=23 4 | TFT_SCLK=18 5 | TFT_CS=15 6 | TFT_DC=2 7 | TFT_RST=4 8 | -------------------------------------------------------------------------------- /tft_config_build_flags.sh: -------------------------------------------------------------------------------- 1 | user_tft_config=$(cat tft_config.txt | tr '\n' ' ' | sed -e "s/\ /\ -D/g" -e "s/-D$//g") 2 | 3 | tft_config="-DUSER_SETUP_LOADED=1 -D${user_tft_config} -DLOAD_GLCD=1 -DLOAD_FONT2=1 -DLOAD_FONT4=1 -DLOAD_FONT6=1 -DLOAD_FONT7=1 -DLOAD_FONT8=1 -DLOAD_GFXFF=1 -DSMOOTH_FONT=1 -DSPI_FREQUENCY=27000000 -DSPI_READ_FREQUENCY=20000000" 4 | echo $tft_config 5 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fossa", 3 | "devices": [ 4 | "esp32" 5 | ], 6 | "versions": [ 7 | "v0.2.0", 8 | "v0.1.0", 9 | "v0.0.0" 10 | ] 11 | } 12 | --------------------------------------------------------------------------------