├── .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 |
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 | 
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 | 
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 | 
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 | Version |
85 | Size |
86 | Error Correction |
87 | Mode |
88 |
89 |
90 | Numeric |
91 | Alphanumeric |
92 | Byte |
93 |
94 |
95 | 1 |
96 | 21 x 21 |
97 | LOW | 41 | 25 | 17 |
98 |
99 |
100 | MEDIUM | 34 | 20 | 14 |
101 |
102 |
103 | QUARTILE | 27 | 16 | 11 |
104 |
105 |
106 | HIGH | 17 | 10 | 7 |
107 |
108 |
109 | 2 |
110 | 25 x 25 |
111 | LOW | 77 | 47 | 32 |
112 |
113 |
114 | MEDIUM | 63 | 38 | 26 |
115 |
116 |
117 | QUARTILE | 48 | 29 | 20 |
118 |
119 |
120 | HIGH | 34 | 20 | 14 |
121 |
122 |
123 | 3 |
124 | 29 x 29 |
125 | LOW | 127 | 77 | 53 |
126 |
127 |
128 | MEDIUM | 101 | 61 | 42 |
129 |
130 |
131 | QUARTILE | 77 | 47 | 32 |
132 |
133 |
134 | HIGH | 58 | 35 | 24 |
135 |
136 |
137 | 4 |
138 | 33 x 33 |
139 | LOW | 187 | 114 | 78 |
140 |
141 |
142 | MEDIUM | 149 | 90 | 62 |
143 |
144 |
145 | QUARTILE | 111 | 67 | 46 |
146 |
147 |
148 | HIGH | 82 | 50 | 34 |
149 |
150 |
151 | 5 |
152 | 37 x 37 |
153 | LOW | 255 | 154 | 106 |
154 |
155 |
156 | MEDIUM | 202 | 122 | 84 |
157 |
158 |
159 | QUARTILE | 144 | 87 | 60 |
160 |
161 |
162 | HIGH | 106 | 64 | 44 |
163 |
164 |
165 | 6 |
166 | 41 x 41 |
167 | LOW | 322 | 195 | 134 |
168 |
169 |
170 | MEDIUM | 255 | 154 | 106 |
171 |
172 |
173 | QUARTILE | 178 | 108 | 74 |
174 |
175 |
176 | HIGH | 139 | 84 | 58 |
177 |
178 |
179 | 7 |
180 | 45 x 45 |
181 | LOW | 370 | 224 | 154 |
182 |
183 |
184 | MEDIUM | 293 | 178 | 122 |
185 |
186 |
187 | QUARTILE | 207 | 125 | 86 |
188 |
189 |
190 | HIGH | 154 | 93 | 64 |
191 |
192 |
193 | 8 |
194 | 49 x 49 |
195 | LOW | 461 | 279 | 192 |
196 |
197 |
198 | MEDIUM | 365 | 221 | 152 |
199 |
200 |
201 | QUARTILE | 259 | 157 | 108 |
202 |
203 |
204 | HIGH | 202 | 122 | 84 |
205 |
206 |
207 | 9 |
208 | 53 x 53 |
209 | LOW | 552 | 335 | 230 |
210 |
211 |
212 | MEDIUM | 432 | 262 | 180 |
213 |
214 |
215 | QUARTILE | 312 | 189 | 130 |
216 |
217 |
218 | HIGH | 235 | 143 | 98 |
219 |
220 |
221 | 10 |
222 | 57 x 57 |
223 | LOW | 652 | 395 | 271 |
224 |
225 |
226 | MEDIUM | 513 | 311 | 213 |
227 |
228 |
229 | QUARTILE | 364 | 221 | 151 |
230 |
231 |
232 | HIGH | 288 | 174 | 119 |
233 |
234 |
235 | 11 |
236 | 61 x 61 |
237 | LOW | 772 | 468 | 321 |
238 |
239 |
240 | MEDIUM | 604 | 366 | 251 |
241 |
242 |
243 | QUARTILE | 427 | 259 | 177 |
244 |
245 |
246 | HIGH | 331 | 200 | 137 |
247 |
248 |
249 | 12 |
250 | 65 x 65 |
251 | LOW | 883 | 535 | 367 |
252 |
253 |
254 | MEDIUM | 691 | 419 | 287 |
255 |
256 |
257 | QUARTILE | 489 | 296 | 203 |
258 |
259 |
260 | HIGH | 374 | 227 | 155 |
261 |
262 |
263 | 13 |
264 | 69 x 69 |
265 | LOW | 1022 | 619 | 425 |
266 |
267 |
268 | MEDIUM | 796 | 483 | 331 |
269 |
270 |
271 | QUARTILE | 580 | 352 | 241 |
272 |
273 |
274 | HIGH | 427 | 259 | 177 |
275 |
276 |
277 | 14 |
278 | 73 x 73 |
279 | LOW | 1101 | 667 | 458 |
280 |
281 |
282 | MEDIUM | 871 | 528 | 362 |
283 |
284 |
285 | QUARTILE | 621 | 376 | 258 |
286 |
287 |
288 | HIGH | 468 | 283 | 194 |
289 |
290 |
291 | 15 |
292 | 77 x 77 |
293 | LOW | 1250 | 758 | 520 |
294 |
295 |
296 | MEDIUM | 991 | 600 | 412 |
297 |
298 |
299 | QUARTILE | 703 | 426 | 292 |
300 |
301 |
302 | HIGH | 530 | 321 | 220 |
303 |
304 |
305 | 16 |
306 | 81 x 81 |
307 | LOW | 1408 | 854 | 586 |
308 |
309 |
310 | MEDIUM | 1082 | 656 | 450 |
311 |
312 |
313 | QUARTILE | 775 | 470 | 322 |
314 |
315 |
316 | HIGH | 602 | 365 | 250 |
317 |
318 |
319 | 17 |
320 | 85 x 85 |
321 | LOW | 1548 | 938 | 644 |
322 |
323 |
324 | MEDIUM | 1212 | 734 | 504 |
325 |
326 |
327 | QUARTILE | 876 | 531 | 364 |
328 |
329 |
330 | HIGH | 674 | 408 | 280 |
331 |
332 |
333 | 18 |
334 | 89 x 89 |
335 | LOW | 1725 | 1046 | 718 |
336 |
337 |
338 | MEDIUM | 1346 | 816 | 560 |
339 |
340 |
341 | QUARTILE | 948 | 574 | 394 |
342 |
343 |
344 | HIGH | 746 | 452 | 310 |
345 |
346 |
347 | 19 |
348 | 93 x 93 |
349 | LOW | 1903 | 1153 | 792 |
350 |
351 |
352 | MEDIUM | 1500 | 909 | 624 |
353 |
354 |
355 | QUARTILE | 1063 | 644 | 442 |
356 |
357 |
358 | HIGH | 813 | 493 | 338 |
359 |
360 |
361 | 20 |
362 | 97 x 97 |
363 | LOW | 2061 | 1249 | 858 |
364 |
365 |
366 | MEDIUM | 1600 | 970 | 666 |
367 |
368 |
369 | QUARTILE | 1159 | 702 | 482 |
370 |
371 |
372 | HIGH | 919 | 557 | 382 |
373 |
374 |
375 | 21 |
376 | 101 x 101 |
377 | LOW | 2232 | 1352 | 929 |
378 |
379 |
380 | MEDIUM | 1708 | 1035 | 711 |
381 |
382 |
383 | QUARTILE | 1224 | 742 | 509 |
384 |
385 |
386 | HIGH | 969 | 587 | 403 |
387 |
388 |
389 | 22 |
390 | 105 x 105 |
391 | LOW | 2409 | 1460 | 1003 |
392 |
393 |
394 | MEDIUM | 1872 | 1134 | 779 |
395 |
396 |
397 | QUARTILE | 1358 | 823 | 565 |
398 |
399 |
400 | HIGH | 1056 | 640 | 439 |
401 |
402 |
403 | 23 |
404 | 109 x 109 |
405 | LOW | 2620 | 1588 | 1091 |
406 |
407 |
408 | MEDIUM | 2059 | 1248 | 857 |
409 |
410 |
411 | QUARTILE | 1468 | 890 | 611 |
412 |
413 |
414 | HIGH | 1108 | 672 | 461 |
415 |
416 |
417 | 24 |
418 | 113 x 113 |
419 | LOW | 2812 | 1704 | 1171 |
420 |
421 |
422 | MEDIUM | 2188 | 1326 | 911 |
423 |
424 |
425 | QUARTILE | 1588 | 963 | 661 |
426 |
427 |
428 | HIGH | 1228 | 744 | 511 |
429 |
430 |
431 | 25 |
432 | 117 x 117 |
433 | LOW | 3057 | 1853 | 1273 |
434 |
435 |
436 | MEDIUM | 2395 | 1451 | 997 |
437 |
438 |
439 | QUARTILE | 1718 | 1041 | 715 |
440 |
441 |
442 | HIGH | 1286 | 779 | 535 |
443 |
444 |
445 | 26 |
446 | 121 x 121 |
447 | LOW | 3283 | 1990 | 1367 |
448 |
449 |
450 | MEDIUM | 2544 | 1542 | 1059 |
451 |
452 |
453 | QUARTILE | 1804 | 1094 | 751 |
454 |
455 |
456 | HIGH | 1425 | 864 | 593 |
457 |
458 |
459 | 27 |
460 | 125 x 125 |
461 | LOW | 3517 | 2132 | 1465 |
462 |
463 |
464 | MEDIUM | 2701 | 1637 | 1125 |
465 |
466 |
467 | QUARTILE | 1933 | 1172 | 805 |
468 |
469 |
470 | HIGH | 1501 | 910 | 625 |
471 |
472 |
473 | 28 |
474 | 129 x 129 |
475 | LOW | 3669 | 2223 | 1528 |
476 |
477 |
478 | MEDIUM | 2857 | 1732 | 1190 |
479 |
480 |
481 | QUARTILE | 2085 | 1263 | 868 |
482 |
483 |
484 | HIGH | 1581 | 958 | 658 |
485 |
486 |
487 | 29 |
488 | 133 x 133 |
489 | LOW | 3909 | 2369 | 1628 |
490 |
491 |
492 | MEDIUM | 3035 | 1839 | 1264 |
493 |
494 |
495 | QUARTILE | 2181 | 1322 | 908 |
496 |
497 |
498 | HIGH | 1677 | 1016 | 698 |
499 |
500 |
501 | 30 |
502 | 137 x 137 |
503 | LOW | 4158 | 2520 | 1732 |
504 |
505 |
506 | MEDIUM | 3289 | 1994 | 1370 |
507 |
508 |
509 | QUARTILE | 2358 | 1429 | 982 |
510 |
511 |
512 | HIGH | 1782 | 1080 | 742 |
513 |
514 |
515 | 31 |
516 | 141 x 141 |
517 | LOW | 4417 | 2677 | 1840 |
518 |
519 |
520 | MEDIUM | 3486 | 2113 | 1452 |
521 |
522 |
523 | QUARTILE | 2473 | 1499 | 1030 |
524 |
525 |
526 | HIGH | 1897 | 1150 | 790 |
527 |
528 |
529 | 32 |
530 | 145 x 145 |
531 | LOW | 4686 | 2840 | 1952 |
532 |
533 |
534 | MEDIUM | 3693 | 2238 | 1538 |
535 |
536 |
537 | QUARTILE | 2670 | 1618 | 1112 |
538 |
539 |
540 | HIGH | 2022 | 1226 | 842 |
541 |
542 |
543 | 33 |
544 | 149 x 149 |
545 | LOW | 4965 | 3009 | 2068 |
546 |
547 |
548 | MEDIUM | 3909 | 2369 | 1628 |
549 |
550 |
551 | QUARTILE | 2805 | 1700 | 1168 |
552 |
553 |
554 | HIGH | 2157 | 1307 | 898 |
555 |
556 |
557 | 34 |
558 | 153 x 153 |
559 | LOW | 5253 | 3183 | 2188 |
560 |
561 |
562 | MEDIUM | 4134 | 2506 | 1722 |
563 |
564 |
565 | QUARTILE | 2949 | 1787 | 1228 |
566 |
567 |
568 | HIGH | 2301 | 1394 | 958 |
569 |
570 |
571 | 35 |
572 | 157 x 157 |
573 | LOW | 5529 | 3351 | 2303 |
574 |
575 |
576 | MEDIUM | 4343 | 2632 | 1809 |
577 |
578 |
579 | QUARTILE | 3081 | 1867 | 1283 |
580 |
581 |
582 | HIGH | 2361 | 1431 | 983 |
583 |
584 |
585 | 36 |
586 | 161 x 161 |
587 | LOW | 5836 | 3537 | 2431 |
588 |
589 |
590 | MEDIUM | 4588 | 2780 | 1911 |
591 |
592 |
593 | QUARTILE | 3244 | 1966 | 1351 |
594 |
595 |
596 | HIGH | 2524 | 1530 | 1051 |
597 |
598 |
599 | 37 |
600 | 165 x 165 |
601 | LOW | 6153 | 3729 | 2563 |
602 |
603 |
604 | MEDIUM | 4775 | 2894 | 1989 |
605 |
606 |
607 | QUARTILE | 3417 | 2071 | 1423 |
608 |
609 |
610 | HIGH | 2625 | 1591 | 1093 |
611 |
612 |
613 | 38 |
614 | 169 x 169 |
615 | LOW | 6479 | 3927 | 2699 |
616 |
617 |
618 | MEDIUM | 5039 | 3054 | 2099 |
619 |
620 |
621 | QUARTILE | 3599 | 2181 | 1499 |
622 |
623 |
624 | HIGH | 2735 | 1658 | 1139 |
625 |
626 |
627 | 39 |
628 | 173 x 173 |
629 | LOW | 6743 | 4087 | 2809 |
630 |
631 |
632 | MEDIUM | 5313 | 3220 | 2213 |
633 |
634 |
635 | QUARTILE | 3791 | 2298 | 1579 |
636 |
637 |
638 | HIGH | 2927 | 1774 | 1219 |
639 |
640 |
641 | 40 |
642 | 177 x 177 |
643 | LOW | 7089 | 4296 | 2953 |
644 |
645 |
646 | MEDIUM | 5596 | 3391 | 2331 |
647 |
648 |
649 | QUARTILE | 3993 | 2420 | 1663 |
650 |
651 |
652 | HIGH | 3057 | 1852 | 1273 |
653 |
654 |
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";
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 |
--------------------------------------------------------------------------------