diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..0205cddad9a9be11c99193c4c834458d3ae3e918 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/YT/* +/enclosure/*.gcode +/rpi_pico_fw/**/fw_test* +/crucible/* diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..26d33521af10bcc7fd8cea344038eaaeb78d0ef5 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..db8786c06ed9719181cd25ebe2615cc99c8490eb --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..afdaab263f7f8bef2d5340601913061669e9c28b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/toysoundmaker.iml b/.idea/toysoundmaker.iml new file mode 100644 index 0000000000000000000000000000000000000000..5fdd65ba2a46cb8059b0b493295bcc0519d4eee1 --- /dev/null +++ b/.idea/toysoundmaker.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 4021d42de67a02e7f1f70774791d7ffcb041099b..b357ce19f91ff05beda9dcbeb1f3e9d7550b2f4d 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,62 @@ # ToySoundMaker +## Electronics, BoM, 3D enclosure and assembly +All those info are on the project page on the main site https://www.hadronrider.uk/node/2/ -## Getting started +## Software -To make it easy for you to get started with GitLab, here's a list of recommended next steps. +### SDK -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! +**This is work in progress.** -## Add your files +### CircuitPython -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: +#### Firmware flashing and deployment -``` -cd existing_repo -git remote add origin https://hadronrider.chickenkiller.com/gitlab/valerio/toysoundmaker.git -git branch -M main -git push -uf origin main -``` +1. Get the RPi firmware here: https://circuitpython.org/board/raspberry_pi_pico/ + 1. Flash it by power cycling your Pi while pressing the BOOTSEL + 2. copy the UF2 file to the Pi as it comes back as a USB drive + 3. the Pi will automatically reboot and reconnect as a COM python console and a USB drive. +2. copy the content of rpi_pico_fw/circuitpython/fw/ to the drive's root +3. copy the audio_samples directory to \\sd -## Integrate with your tools +#### Thonny IDE -- [ ] [Set up project integrations](https://hadronrider.chickenkiller.com/gitlab/valerio/toysoundmaker/-/settings/integrations) +You can use a serial console terminal like Putty to interact with the CircuitPython console and an editor to edit code.py directly on the SD, but you can also download [Thonny IDE](https://thonny.org/), a tool that integrates a multitab editor, the console terminal and a debugger. -## Collaborate with your team +It's portable, lightweight and works with CPY or uPY -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) +#### Build the Adafruit libraries -## Test and Deploy +This is only needed to create/update the PN532 or other libraries. Not needed to make changes to code.py, only meant for later reference, new projects/major revamps. -Use the built-in continuous integration in GitLab. +This only works on linux so make sure you get a VM or Docker/WSL2 container if you're on Windows. -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) +Always a good idea to create a python virtual environment, wherever you want: -*** +`python3 -m venv pyvenv` -# Editing this README +`. pyvenv/bin/activate` -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. +Install the libraries: -## Suggestions for a good README +`pip3 install adafruit-circuitpython-lis3dh` -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. +`pip3 install circuitpython-build-tools` -## Name -Choose a self-explaining name for your project. +Now the PN532 library is already included in the project but should you need to rebuild them, carry on: -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. +`git clone https://github.com/adafruit/Adafruit_CircuitPython_Bundle.git` -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. +`cd Adafruit_CircuitPython_Bundle` -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. +`git submodule init` -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +`git submodule update` -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. +`circuitpython-build-bundles --filename_prefix adafruit-circuitpython-bundle --library_location libraries --library_depth 2` -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. +You can find libraries to put in your project here: build-adafruit-circuitpython-bundle-9.x-mpy-20241023.zip\adafruit-circuitpython-bundle-9.x-mpy-20241023\lib -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +*More at https://github.com/adafruit/Adafruit_CircuitPython_Bundle* diff --git a/audio_samples/chase_01.mp3 b/audio_samples/chase_01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4f18183a951b6351d5289448ff4f5b5d85ba6da7 Binary files /dev/null and b/audio_samples/chase_01.mp3 differ diff --git a/audio_samples/chase_02.mp3 b/audio_samples/chase_02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..3e2ebeaf7545e1b946f63684f6937035c4df53b1 Binary files /dev/null and b/audio_samples/chase_02.mp3 differ diff --git a/audio_samples/chase_03.mp3 b/audio_samples/chase_03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4c37db27f7d71f86bfd3c701a6ce531ccd1283e2 Binary files /dev/null and b/audio_samples/chase_03.mp3 differ diff --git a/audio_samples/chase_04.mp3 b/audio_samples/chase_04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..cdfa6ce7d023d98567a2aff7260a4f22817754f3 Binary files /dev/null and b/audio_samples/chase_04.mp3 differ diff --git a/audio_samples/everest_01.mp3 b/audio_samples/everest_01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..cc94b3411d81c270ed1546a812e13cd1c1c555fe Binary files /dev/null and b/audio_samples/everest_01.mp3 differ diff --git a/audio_samples/everest_02.mp3 b/audio_samples/everest_02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..9772408caaddc9b7bf2ff20c5a549ca68343d8d7 Binary files /dev/null and b/audio_samples/everest_02.mp3 differ diff --git a/audio_samples/everest_03.mp3 b/audio_samples/everest_03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7a31184349aea44a2e985b886a42b620ed999c22 Binary files /dev/null and b/audio_samples/everest_03.mp3 differ diff --git a/audio_samples/marshall_01.mp3 b/audio_samples/marshall_01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0129cf21ff1a6f43b80e815dd1e48bf676cc22c8 Binary files /dev/null and b/audio_samples/marshall_01.mp3 differ diff --git a/audio_samples/marshall_02.mp3 b/audio_samples/marshall_02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a4457595db5d5732c43484054daf62bc9584280c Binary files /dev/null and b/audio_samples/marshall_02.mp3 differ diff --git a/audio_samples/marshall_03.mp3 b/audio_samples/marshall_03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..9f5ec17a5623b7fa031e68b66348f4b7fd2335c1 Binary files /dev/null and b/audio_samples/marshall_03.mp3 differ diff --git a/audio_samples/marshall_04.mp3 b/audio_samples/marshall_04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f8600048903eb3ef4d045190d9730abcc32cdff9 Binary files /dev/null and b/audio_samples/marshall_04.mp3 differ diff --git a/audio_samples/rocky_01.mp3 b/audio_samples/rocky_01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0e82378f9a9ae771c2b690244e1f0761d9756011 Binary files /dev/null and b/audio_samples/rocky_01.mp3 differ diff --git a/audio_samples/rocky_02.mp3 b/audio_samples/rocky_02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e680e44b9071eb8c75757666da1a84e245459777 Binary files /dev/null and b/audio_samples/rocky_02.mp3 differ diff --git a/audio_samples/rocky_03.mp3 b/audio_samples/rocky_03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0091cd9764a0b6e92df8628ca806c64adddf4402 Binary files /dev/null and b/audio_samples/rocky_03.mp3 differ diff --git a/audio_samples/rocky_04.mp3 b/audio_samples/rocky_04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..c36b7fff1dab47303a16fe88be91818f1f1631e3 Binary files /dev/null and b/audio_samples/rocky_04.mp3 differ diff --git a/audio_samples/rubble_01.mp3 b/audio_samples/rubble_01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d1dce5fb2d977f46d5fc8a4ff319786ab10c1c13 Binary files /dev/null and b/audio_samples/rubble_01.mp3 differ diff --git a/audio_samples/rubble_02.mp3 b/audio_samples/rubble_02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..665211d47e5e266018cc8be334209ecece52d900 Binary files /dev/null and b/audio_samples/rubble_02.mp3 differ diff --git a/audio_samples/rubble_03.mp3 b/audio_samples/rubble_03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..da04ddbc5b284c596d7d0d87ba93bbf2c196d986 Binary files /dev/null and b/audio_samples/rubble_03.mp3 differ diff --git a/audio_samples/rubble_04.mp3 b/audio_samples/rubble_04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1983bd467d2a14ee45bb50095926c1f16fb31b2b Binary files /dev/null and b/audio_samples/rubble_04.mp3 differ diff --git a/audio_samples/rubble_05.mp3 b/audio_samples/rubble_05.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6419153a0450263c7c3c8707bd4771ad433c205f Binary files /dev/null and b/audio_samples/rubble_05.mp3 differ diff --git a/audio_samples/skye_01.mp3 b/audio_samples/skye_01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..c17e9fabbadea385a33341a98054fdb34541d9c5 Binary files /dev/null and b/audio_samples/skye_01.mp3 differ diff --git a/audio_samples/skye_02.mp3 b/audio_samples/skye_02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e4b2d1887d6a5243ea766452cdb75a6fa914cbd8 Binary files /dev/null and b/audio_samples/skye_02.mp3 differ diff --git a/audio_samples/skye_03.mp3 b/audio_samples/skye_03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e2276c49e8132361cc2f32caa42c2629a230bd0d Binary files /dev/null and b/audio_samples/skye_03.mp3 differ diff --git a/audio_samples/skye_04.mp3 b/audio_samples/skye_04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6327ae64bf2e8920b0fb8b9f843235dc78c6e411 Binary files /dev/null and b/audio_samples/skye_04.mp3 differ diff --git a/audio_samples/skye_05.mp3 b/audio_samples/skye_05.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a9de94876d40088b008294dd279a30a0f1d73cce Binary files /dev/null and b/audio_samples/skye_05.mp3 differ diff --git a/audio_samples/sweetie_01.mp3 b/audio_samples/sweetie_01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e0c1ec3c35055d1865c66e71b41df9cdcfad9b24 Binary files /dev/null and b/audio_samples/sweetie_01.mp3 differ diff --git a/audio_samples/sweetie_02.mp3 b/audio_samples/sweetie_02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d454721d3d43c8aaeca67c2b81c67896d2f284cc Binary files /dev/null and b/audio_samples/sweetie_02.mp3 differ diff --git a/audio_samples/sweetie_03.mp3 b/audio_samples/sweetie_03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..64d9c577414bfe775c40b9ba3259b6fafb4b3081 Binary files /dev/null and b/audio_samples/sweetie_03.mp3 differ diff --git a/audio_samples/sweetie_04.mp3 b/audio_samples/sweetie_04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..bf4cf2be5b43a2f4f6bc61b88ec6aeaafc1c10e0 Binary files /dev/null and b/audio_samples/sweetie_04.mp3 differ diff --git a/audio_samples/tracker_01.mp3 b/audio_samples/tracker_01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2661165bb8c2046a25b26b843ff077b0794e59c9 Binary files /dev/null and b/audio_samples/tracker_01.mp3 differ diff --git a/audio_samples/tracker_02.mp3 b/audio_samples/tracker_02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6746deccbeb5027764b8291d02dcaa29595562b2 Binary files /dev/null and b/audio_samples/tracker_02.mp3 differ diff --git a/audio_samples/tracker_03.mp3 b/audio_samples/tracker_03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..01ef1c2668f1b7725ac7b34b12e900448b6c3fe5 Binary files /dev/null and b/audio_samples/tracker_03.mp3 differ diff --git a/audio_samples/tracker_04.mp3 b/audio_samples/tracker_04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ba373e6797119b8dba13b28137989a448ee7fc4e Binary files /dev/null and b/audio_samples/tracker_04.mp3 differ diff --git a/audio_samples/tracker_05.mp3 b/audio_samples/tracker_05.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..bf91e67ff9cb5529121926bdc9929e78af5b22f6 Binary files /dev/null and b/audio_samples/tracker_05.mp3 differ diff --git a/audio_samples/zuma_01.mp3 b/audio_samples/zuma_01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..42eb19fdf7768ed28546ff397a52d73d5f3f8b05 Binary files /dev/null and b/audio_samples/zuma_01.mp3 differ diff --git a/audio_samples/zuma_02.mp3 b/audio_samples/zuma_02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0da6595bcc3194b10919f6d06575b4283b7705e2 Binary files /dev/null and b/audio_samples/zuma_02.mp3 differ diff --git a/audio_samples/zuma_03.mp3 b/audio_samples/zuma_03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..871a68380f46e1c6dde25b3e473996cc6b569c92 Binary files /dev/null and b/audio_samples/zuma_03.mp3 differ diff --git a/enclosure/2mm_washer-Body.stl b/enclosure/2mm_washer-Body.stl new file mode 100644 index 0000000000000000000000000000000000000000..42a12acb744f82664ab415a3c5fa49858f6c9d6d Binary files /dev/null and b/enclosure/2mm_washer-Body.stl differ diff --git a/enclosure/2mm_washer.FCStd b/enclosure/2mm_washer.FCStd new file mode 100644 index 0000000000000000000000000000000000000000..ce7d3df6208e02cf44a55e30c615032fae209930 Binary files /dev/null and b/enclosure/2mm_washer.FCStd differ diff --git a/enclosure/3mm_washer-Body.stl b/enclosure/3mm_washer-Body.stl new file mode 100644 index 0000000000000000000000000000000000000000..c3fc14f5c396d211504ae9decfe6e0666fd47236 Binary files /dev/null and b/enclosure/3mm_washer-Body.stl differ diff --git a/enclosure/3mm_washer.FCStd b/enclosure/3mm_washer.FCStd new file mode 100644 index 0000000000000000000000000000000000000000..27b97c384a608a3d3e25b9897fa4975defb54f03 Binary files /dev/null and b/enclosure/3mm_washer.FCStd differ diff --git a/enclosure/bottom-Body.stl b/enclosure/bottom-Body.stl new file mode 100644 index 0000000000000000000000000000000000000000..c120068bf532f33d28e42bbbd57da84d4a621bda Binary files /dev/null and b/enclosure/bottom-Body.stl differ diff --git a/enclosure/bottom.FCStd b/enclosure/bottom.FCStd new file mode 100644 index 0000000000000000000000000000000000000000..08baae243e422940c26f51964846500e36c6a7e2 Binary files /dev/null and b/enclosure/bottom.FCStd differ diff --git a/enclosure/speaker_holding_ring-Body.stl b/enclosure/speaker_holding_ring-Body.stl new file mode 100644 index 0000000000000000000000000000000000000000..e78340745aac687de15d073682df14eebebcc4f9 Binary files /dev/null and b/enclosure/speaker_holding_ring-Body.stl differ diff --git a/enclosure/speaker_holding_ring.FCStd b/enclosure/speaker_holding_ring.FCStd new file mode 100644 index 0000000000000000000000000000000000000000..cc4ac686bcf5d397a879f36bf53a405f44be59d0 Binary files /dev/null and b/enclosure/speaker_holding_ring.FCStd differ diff --git a/enclosure/top-Body.stl b/enclosure/top-Body.stl new file mode 100644 index 0000000000000000000000000000000000000000..ad2457251366f65085b90841b905c39addd62723 Binary files /dev/null and b/enclosure/top-Body.stl differ diff --git a/enclosure/top.FCStd b/enclosure/top.FCStd new file mode 100644 index 0000000000000000000000000000000000000000..a205a99e3e9f6fb93a562251ab5f0a7124895564 Binary files /dev/null and b/enclosure/top.FCStd differ diff --git a/rpi_pico_fw/circuitpython/adafruit-circuitpython-raspberry_pi_pico-en_GB-9.0.5.uf2 b/rpi_pico_fw/circuitpython/adafruit-circuitpython-raspberry_pi_pico-en_GB-9.0.5.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..f4a75da664d162dbe8d68dba66441539e8f3cc8f Binary files /dev/null and b/rpi_pico_fw/circuitpython/adafruit-circuitpython-raspberry_pi_pico-en_GB-9.0.5.uf2 differ diff --git a/rpi_pico_fw/circuitpython/adafruit-circuitpython-raspberry_pi_pico-en_GB-9.1.4.uf2 b/rpi_pico_fw/circuitpython/adafruit-circuitpython-raspberry_pi_pico-en_GB-9.1.4.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..26a6f9b0c238ac93b805267d4a8f2785529e9007 Binary files /dev/null and b/rpi_pico_fw/circuitpython/adafruit-circuitpython-raspberry_pi_pico-en_GB-9.1.4.uf2 differ diff --git a/rpi_pico_fw/circuitpython/build_circuitpython_libs.sh b/rpi_pico_fw/circuitpython/build_circuitpython_libs.sh new file mode 100644 index 0000000000000000000000000000000000000000..93496428120e0b7d16736297d5a2125b36da4dd9 --- /dev/null +++ b/rpi_pico_fw/circuitpython/build_circuitpython_libs.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# From https://github.com/adafruit/Adafruit_CircuitPython_Bundle + +python3 -m venv pyvenv +. pyvenv/bin/activate + +pip3 install adafruit-circuitpython-lis3dh +pip3 install circuitpython-build-tools + +git clone https://github.com/adafruit/Adafruit_CircuitPython_Bundle.git +cd Adafruit_CircuitPython_Bundle +git submodule init +git submodule update + +circuitpython-build-bundles --filename_prefix adafruit-circuitpython-bundle --library_location libraries --library_depth 2 diff --git a/rpi_pico_fw/circuitpython/fw/code.py b/rpi_pico_fw/circuitpython/fw/code.py new file mode 100644 index 0000000000000000000000000000000000000000..00c7f84a77cb2e62da558d35ba20a06cb1975812 --- /dev/null +++ b/rpi_pico_fw/circuitpython/fw/code.py @@ -0,0 +1,142 @@ +""" +Toy sound maker + +Listens for RFID tags and if one among a list is detected, play an MP3 from the associated pool. + +Deployment instructions: +- put this file in the root directory +- put the audio samples in \sd\audio_samples, e.g. \sd\audio_samples\marshall_01.mp3 +- put the adafruit_pn532 library folder in \lib + +Credit to ladyada and Kattni Rembor from Adafruit Industries for the example code! +""" +import time +import asyncio + +import busio +import board +import pwmio +from digitalio import DigitalInOut + +import audiomp3 +import audiopwmio + +from adafruit_pn532.i2c import PN532_I2C + + +RFID_IDS_MP3_DICT = { + b'\x00\x00\x00\x00\x00\x00\x00': {'samples': ['rocky_01.mp3', 'rocky_02.mp3', 'rocky_03.mp3', 'rocky_04.mp3'], 'index': 0}, + b'\x00\x00\x00\x00\x00\x00\x01': {'samples': ['skye_01.mp3', 'skye_02.mp3', 'skye_03.mp3', 'skye_04.mp3', 'skye_05.mp3'], 'index': 0}, + # ... + } + + +async def blink_led(): + """ + Blink the LED, with a PWM + """ + print('LED init...') + + # led = pwmio.PWMOut(board.LED, frequency=5000, duty_cycle=0) + led = pwmio.PWMOut(board.GP28, frequency=5000, duty_cycle=0) + + led.duty_cycle = int(25 * 65535 / 100) + + # while True: + # # # Fade in + # # for i in range(25): + # # led.duty_cycle = int(i * 2 * 65535 / 100) + # # await asyncio.sleep(0.02) + # # # Fade out + # # for i in range(25, 0, -1): + # # led.duty_cycle = int(i * 2 * 65535 / 100) + # # await asyncio.sleep(0.02) + + # # led.duty_cycle = int(0) + # # await asyncio.sleep(0.5) + + # led.duty_cycle = int(0) + # await asyncio.sleep(2) + # led.duty_cycle = int(50 * 65535 / 100) + # await asyncio.sleep(0.1) + + +async def main_loop(): + """ + Main NFC and playback polling + """ + print('Audio init...') + + # MP3 player + audioplayer = audiopwmio.PWMAudioOut(board.GP0) + + print('Starting PN532 NFC reader...') + + # I2C bus manager, the NFC reader's lower layer + i2c = busio.I2C(board.GP13, board.GP12) + + # NFC reader's hardware reset pin + reset_pin = DigitalInOut(board.GP10) + # NFC reader's IRQ pin + irq_pin = DigitalInOut(board.GP11) + pn532 = PN532_I2C(i2c, debug=False, reset=reset_pin, req=irq_pin) + + # Configure PN532 to communicate with MiFare cards + pn532.SAM_configuration() + + ic, ver, rev, support = pn532.firmware_version + print('Found PN532 with firmware version: {0}.{1}'.format(ver, rev)) + + old_uid = None + + print('Waiting for RFID/NFC card...') + while True: + # Check if a card is available to read + new_uid = pn532.read_passive_target(timeout=0.95) + await asyncio.sleep(0.05) + + print('.', end='') + + # Try again if no card is available. + if new_uid is None: + old_uid = new_uid + continue + + # As long as the tag is close, read_passive_target will keep returning its ID so wait for a new one instead of playing agian + if True: + for pup_uid, audiorec in RFID_IDS_MP3_DICT.items(): + if new_uid == pup_uid: + samples = audiorec['samples'] + i = audiorec['index'] + print('Found {}, playing {}'.format([hex(e) for e in new_uid], samples[i])) + + decoder = audiomp3.MP3Decoder(open('sd/audio_samples/' + samples[i], 'rb')) + + # Move to the next sample next + audiorec['index'] += 1 + audiorec['index'] %= len(samples) + + audioplayer.play(decoder) + while audioplayer.playing: + pass + + old_uid = new_uid + break + else: + print('Found {}, unknown tag'.format([hex(e) for e in new_uid])) + + +async def main(): + """ + Start the threads and wait for them forever + """ + blink_led_task = asyncio.create_task(blink_led()) + main_loop_task = asyncio.create_task(main_loop()) + + await asyncio.gather(blink_led_task, main_loop_task) + # await asyncio.gather(blink_led_task) + + +asyncio.run(main()) + + diff --git a/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/adafruit_pn532.py b/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/adafruit_pn532.py new file mode 100644 index 0000000000000000000000000000000000000000..6d8d2f229a9c3ca1fa13a5341971f0e15118688b --- /dev/null +++ b/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/adafruit_pn532.py @@ -0,0 +1,630 @@ +# SPDX-FileCopyrightText: 2015-2018 Tony DiCola for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +``adafruit_pn532`` +==================================================== + +This module will let you communicate with a PN532 RFID/NFC shield or breakout +using I2C, SPI or UART. + +* Author(s): Original Raspberry Pi code by Tony DiCola, CircuitPython by ladyada + +Implementation Notes +-------------------- + +**Hardware:** + +* Adafruit `PN532 Breakout `_ +* Adafruit `PN532 Shield `_ + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases +* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice +""" + +import time +import struct +from digitalio import Direction +from micropython import const + +try: + from typing import Optional, Tuple, Union + from typing_extensions import Literal + from circuitpython_typing import ReadableBuffer + from digitalio import DigitalInOut # pylint: disable=ungrouped-imports +except ImportError: + pass + +__version__ = "2.4.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PN532.git" + +_PREAMBLE = const(0x00) +_STARTCODE1 = const(0x00) +_STARTCODE2 = const(0xFF) +_POSTAMBLE = const(0x00) + +_HOSTTOPN532 = const(0xD4) +_PN532TOHOST = const(0xD5) + +# PN532 Commands +_COMMAND_DIAGNOSE = const(0x00) +_COMMAND_GETFIRMWAREVERSION = const(0x02) +_COMMAND_GETGENERALSTATUS = const(0x04) +_COMMAND_READREGISTER = const(0x06) +_COMMAND_WRITEREGISTER = const(0x08) +_COMMAND_READGPIO = const(0x0C) +_COMMAND_WRITEGPIO = const(0x0E) +_COMMAND_SETSERIALBAUDRATE = const(0x10) +_COMMAND_SETPARAMETERS = const(0x12) +_COMMAND_SAMCONFIGURATION = const(0x14) +_COMMAND_POWERDOWN = const(0x16) +_COMMAND_RFCONFIGURATION = const(0x32) +_COMMAND_RFREGULATIONTEST = const(0x58) +_COMMAND_INJUMPFORDEP = const(0x56) +_COMMAND_INJUMPFORPSL = const(0x46) +_COMMAND_INLISTPASSIVETARGET = const(0x4A) +_COMMAND_INATR = const(0x50) +_COMMAND_INPSL = const(0x4E) +_COMMAND_INDATAEXCHANGE = const(0x40) +_COMMAND_INCOMMUNICATETHRU = const(0x42) +_COMMAND_INDESELECT = const(0x44) +_COMMAND_INRELEASE = const(0x52) +_COMMAND_INSELECT = const(0x54) +_COMMAND_INAUTOPOLL = const(0x60) +_COMMAND_TGINITASTARGET = const(0x8C) +_COMMAND_TGSETGENERALBYTES = const(0x92) +_COMMAND_TGGETDATA = const(0x86) +_COMMAND_TGSETDATA = const(0x8E) +_COMMAND_TGSETMETADATA = const(0x94) +_COMMAND_TGGETINITIATORCOMMAND = const(0x88) +_COMMAND_TGRESPONSETOINITIATOR = const(0x90) +_COMMAND_TGGETTARGETSTATUS = const(0x8A) + +_RESPONSE_INDATAEXCHANGE = const(0x41) +_RESPONSE_INLISTPASSIVETARGET = const(0x4B) + +_WAKEUP = const(0x55) + +_MIFARE_ISO14443A = const(0x00) + +# Mifare Commands +MIFARE_CMD_AUTH_A = const(0x60) +MIFARE_CMD_AUTH_B = const(0x61) +MIFARE_CMD_READ = const(0x30) +MIFARE_CMD_WRITE = const(0xA0) +MIFARE_CMD_TRANSFER = const(0xB0) +MIFARE_CMD_DECREMENT = const(0xC0) +MIFARE_CMD_INCREMENT = const(0xC1) +MIFARE_CMD_STORE = const(0xC2) +MIFARE_ULTRALIGHT_CMD_WRITE = const(0xA2) + +# Prefixes for NDEF Records (to identify record type) +NDEF_URIPREFIX_NONE = const(0x00) +NDEF_URIPREFIX_HTTP_WWWDOT = const(0x01) +NDEF_URIPREFIX_HTTPS_WWWDOT = const(0x02) +NDEF_URIPREFIX_HTTP = const(0x03) +NDEF_URIPREFIX_HTTPS = const(0x04) +NDEF_URIPREFIX_TEL = const(0x05) +NDEF_URIPREFIX_MAILTO = const(0x06) +NDEF_URIPREFIX_FTP_ANONAT = const(0x07) +NDEF_URIPREFIX_FTP_FTPDOT = const(0x08) +NDEF_URIPREFIX_FTPS = const(0x09) +NDEF_URIPREFIX_SFTP = const(0x0A) +NDEF_URIPREFIX_SMB = const(0x0B) +NDEF_URIPREFIX_NFS = const(0x0C) +NDEF_URIPREFIX_FTP = const(0x0D) +NDEF_URIPREFIX_DAV = const(0x0E) +NDEF_URIPREFIX_NEWS = const(0x0F) +NDEF_URIPREFIX_TELNET = const(0x10) +NDEF_URIPREFIX_IMAP = const(0x11) +NDEF_URIPREFIX_RTSP = const(0x12) +NDEF_URIPREFIX_URN = const(0x13) +NDEF_URIPREFIX_POP = const(0x14) +NDEF_URIPREFIX_SIP = const(0x15) +NDEF_URIPREFIX_SIPS = const(0x16) +NDEF_URIPREFIX_TFTP = const(0x17) +NDEF_URIPREFIX_BTSPP = const(0x18) +NDEF_URIPREFIX_BTL2CAP = const(0x19) +NDEF_URIPREFIX_BTGOEP = const(0x1A) +NDEF_URIPREFIX_TCPOBEX = const(0x1B) +NDEF_URIPREFIX_IRDAOBEX = const(0x1C) +NDEF_URIPREFIX_FILE = const(0x1D) +NDEF_URIPREFIX_URN_EPC_ID = const(0x1E) +NDEF_URIPREFIX_URN_EPC_TAG = const(0x1F) +NDEF_URIPREFIX_URN_EPC_PAT = const(0x20) +NDEF_URIPREFIX_URN_EPC_RAW = const(0x21) +NDEF_URIPREFIX_URN_EPC = const(0x22) +NDEF_URIPREFIX_URN_NFC = const(0x23) + +_GPIO_VALIDATIONBIT = const(0x80) +_GPIO_P30 = const(0) +_GPIO_P31 = const(1) +_GPIO_P32 = const(2) +_GPIO_P33 = const(3) +_GPIO_P34 = const(4) +_GPIO_P35 = const(5) + +_ACK = b"\x00\x00\xFF\x00\xFF\x00" +_FRAME_START = b"\x00\x00\xFF" + + +class BusyError(Exception): + """Base class for exceptions in this module.""" + + +class PN532: + """PN532 driver base, must be extended for I2C/SPI/UART interfacing""" + + def __init__( + self, + *, + debug: bool = False, + irq: Optional[DigitalInOut] = None, + reset: Optional[DigitalInOut] = None, + ) -> None: + """Create an instance of the PN532 class""" + self.low_power = True + self.debug = debug + self._irq = irq + self._reset_pin = reset + self.reset() + _ = self.firmware_version + + def _read_data(self, count: int) -> Union[bytes, bytearray]: + # Read raw data from device, not including status bytes: + # Subclasses MUST implement this! + raise NotImplementedError + + def _write_data(self, framebytes: bytes) -> None: + # Write raw bytestring data to device, not including status bytes: + # Subclasses MUST implement this! + raise NotImplementedError + + def _wait_ready(self, timeout: float) -> bool: + # Check if busy up to max length of 'timeout' seconds + # Subclasses MUST implement this! + raise NotImplementedError + + def _wakeup(self) -> None: + # Send special command to wake up + raise NotImplementedError + + def reset(self) -> None: + """Perform a hardware reset toggle and then wake up the PN532""" + if self._reset_pin: + if self.debug: + print("Resetting") + self._reset_pin.direction = Direction.OUTPUT + self._reset_pin.value = False + time.sleep(0.1) + self._reset_pin.value = True + time.sleep(0.1) + self._wakeup() + + def _write_frame(self, data: bytearray) -> None: + """Write a frame to the PN532 with the specified data bytearray.""" + assert ( + data is not None and 1 < len(data) < 255 + ), "Data must be array of 1 to 255 bytes." + # Build frame to send as: + # - Preamble (0x00) + # - Start code (0x00, 0xFF) + # - Command length (1 byte) + # - Command length checksum + # - Command bytes + # - Checksum + # - Postamble (0x00) + length = len(data) + frame = bytearray(length + 8) + frame[0] = _PREAMBLE + frame[1] = _STARTCODE1 + frame[2] = _STARTCODE2 + checksum = sum(frame[0:3]) + frame[3] = length & 0xFF + frame[4] = (~length + 1) & 0xFF + frame[5:-2] = data + checksum += sum(data) + frame[-2] = ~checksum & 0xFF + frame[-1] = _POSTAMBLE + # Send frame. + if self.debug: + print("Write frame: ", [hex(i) for i in frame]) + self._write_data(bytes(frame)) + + def _read_frame(self, length: int) -> Union[bytes, bytearray]: + """Read a response frame from the PN532 of at most length bytes in size. + Returns the data inside the frame if found, otherwise raises an exception + if there is an error parsing the frame. Note that less than length bytes + might be returned! + """ + # Read frame with expected length of data. + response = self._read_data(length + 7) + if self.debug: + print("Read frame:", [hex(i) for i in response]) + + # Swallow all the 0x00 values that preceed 0xFF. + offset = 0 + while response[offset] == 0x00: + offset += 1 + if offset >= len(response): + raise RuntimeError("Response frame preamble does not contain 0x00FF!") + if response[offset] != 0xFF: + raise RuntimeError("Response frame preamble does not contain 0x00FF!") + offset += 1 + if offset >= len(response): + raise RuntimeError("Response contains no data!") + # Check length & length checksum match. + frame_len = response[offset] + if (frame_len + response[offset + 1]) & 0xFF != 0: + raise RuntimeError("Response length checksum did not match length!") + # Check frame checksum value matches bytes. + checksum = sum(response[offset + 2 : offset + 2 + frame_len + 1]) & 0xFF + if checksum != 0: + raise RuntimeError( + "Response checksum did not match expected value: ", checksum + ) + # Return frame data. + return response[offset + 2 : offset + 2 + frame_len] + + def call_function( + self, + command: int, + response_length: int = 0, + params: ReadableBuffer = b"", + timeout: float = 1, + ) -> Optional[Union[bytes, bytearray]]: + """Send specified command to the PN532 and expect up to response_length + bytes back in a response. Note that less than the expected bytes might + be returned! Params can optionally specify an array of bytes to send as + parameters to the function call. Will wait up to timeout seconds + for a response and return a bytearray of response bytes, or None if no + response is available within the timeout. + """ + if not self.send_command(command, params=params, timeout=timeout): + return None + return self.process_response( + command, response_length=response_length, timeout=timeout + ) + + def send_command( + self, command: int, params: ReadableBuffer = b"", timeout: float = 1 + ) -> bool: + """Send specified command to the PN532 and wait for an acknowledgment. + Will wait up to timeout seconds for the acknowledgment and return True. + If no acknowledgment is received, False is returned. + """ + if self.low_power: + self._wakeup() + + # Build frame data with command and parameters. + data = bytearray(2 + len(params)) + data[0] = _HOSTTOPN532 + data[1] = command & 0xFF + for i, val in enumerate(params): + data[2 + i] = val + # Send frame and wait for response. + try: + self._write_frame(data) + except OSError: + return False + if not self._wait_ready(timeout): + return False + # Verify ACK response and wait to be ready for function response. + if not _ACK == self._read_data(len(_ACK)): + raise RuntimeError("Did not receive expected ACK from PN532!") + return True + + def process_response( + self, command: int, response_length: int = 0, timeout: float = 1 + ) -> Optional[Union[bytes, bytearray]]: + """Process the response from the PN532 and expect up to response_length + bytes back in a response. Note that less than the expected bytes might + be returned! Will wait up to timeout seconds for a response and return + a bytearray of response bytes, or None if no response is available + within the timeout. + """ + if not self._wait_ready(timeout): + return None + # Read response bytes. + response = self._read_frame(response_length + 2) + # Check that response is for the called function. + if not (response[0] == _PN532TOHOST and response[1] == (command + 1)): + raise RuntimeError("Received unexpected command response!") + # Return response data. + return response[2:] + + def power_down(self) -> bool: + """Put the PN532 into a low power state. If the reset pin is connected a + hard power down is performed, if not, a soft power down is performed + instead. Returns True if the PN532 was powered down successfully or + False if not.""" + if self._reset_pin: # Hard Power Down if the reset pin is connected + self._reset_pin.value = False + self.low_power = True + else: + # Soft Power Down otherwise. Enable wakeup on I2C, SPI, UART + response = self.call_function(_COMMAND_POWERDOWN, params=[0xB0, 0x00]) + self.low_power = response[0] == 0x00 + time.sleep(0.005) + return self.low_power + + @property + def firmware_version(self) -> Tuple[int, int, int, int]: + """Call PN532 GetFirmwareVersion function and return a tuple with the IC, + Ver, Rev, and Support values. + """ + response = self.call_function(_COMMAND_GETFIRMWAREVERSION, 4, timeout=0.5) + if response is None: + raise RuntimeError("Failed to detect the PN532") + return tuple(response) + + def SAM_configuration(self) -> None: # pylint: disable=invalid-name + """Configure the PN532 to read MiFare cards.""" + # Send SAM configuration command with configuration for: + # - 0x01, normal mode + # - 0x14, timeout 50ms * 20 = 1 second + # - 0x01, use IRQ pin + # Note that no other verification is necessary as call_function will + # check the command was executed as expected. + self.call_function(_COMMAND_SAMCONFIGURATION, params=[0x01, 0x14, 0x01]) + + def read_passive_target( + self, card_baud: int = _MIFARE_ISO14443A, timeout: float = 1 + ) -> Optional[bytearray]: + """Wait for a MiFare card to be available and return its UID when found. + Will wait up to timeout seconds and return None if no card is found, + otherwise a bytearray with the UID of the found card is returned. + """ + # Send passive read command for 1 card. Expect at most a 7 byte UUID. + response = self.listen_for_passive_target(card_baud=card_baud, timeout=timeout) + # If no response is available return None to indicate no card is present. + if not response: + return None + return self.get_passive_target(timeout=timeout) + + def listen_for_passive_target( + self, card_baud: int = _MIFARE_ISO14443A, timeout: float = 1 + ) -> bool: + """Send command to PN532 to begin listening for a Mifare card. This + returns True if the command was received successfully. Note, this does + not also return the UID of a card! `get_passive_target` must be called + to read the UID when a card is found. If just looking to see if a card + is currently present use `read_passive_target` instead. + """ + # Send passive read command for 1 card. Expect at most a 7 byte UUID. + try: + response = self.send_command( + _COMMAND_INLISTPASSIVETARGET, params=[0x01, card_baud], timeout=timeout + ) + except BusyError: + return False # _COMMAND_INLISTPASSIVETARGET failed + return response + + def get_passive_target( + self, timeout: float = 1 + ) -> Optional[Union[bytes, bytearray]]: + """Will wait up to timeout seconds and return None if no card is found, + otherwise a bytearray with the UID of the found card is returned. + `listen_for_passive_target` must have been called first in order to put + the PN532 into a listening mode. + + It can be useful to use this when using the IRQ pin. Use the IRQ pin to + detect when a card is present and then call this function to read the + card's UID. This reduces the amount of time spend checking for a card. + """ + response = self.process_response( + _COMMAND_INLISTPASSIVETARGET, response_length=30, timeout=timeout + ) + # If no response is available return None to indicate no card is present. + if response is None: + return None + # Check only 1 card with up to a 7 byte UID is present. + if response[0] != 0x01: + raise RuntimeError("More than one card detected!") + if response[5] > 7: + raise RuntimeError("Found card with unexpectedly long UID!") + # Return UID of card. + return response[6 : 6 + response[5]] + + def mifare_classic_authenticate_block( # pylint: disable=invalid-name + self, + uid: ReadableBuffer, + block_number: int, + key_number: Literal[0x60, 0x61], + key: ReadableBuffer, + ) -> bool: + """Authenticate specified block number for a MiFare classic card. Uid + should be a byte array with the UID of the card, block number should be + the block to authenticate, key number should be the key type (like + MIFARE_CMD_AUTH_A or MIFARE_CMD_AUTH_B), and key should be a byte array + with the key data. Returns True if the block was authenticated, or False + if not authenticated. + """ + # Build parameters for InDataExchange command to authenticate MiFare card. + uidlen = len(uid) + keylen = len(key) + params = bytearray(3 + uidlen + keylen) + params[0] = 0x01 # Max card numbers + params[1] = key_number & 0xFF + params[2] = block_number & 0xFF + params[3 : 3 + keylen] = key + params[3 + keylen :] = uid + # Send InDataExchange request and verify response is 0x00. + response = self.call_function( + _COMMAND_INDATAEXCHANGE, params=params, response_length=1 + ) + return response[0] == 0x00 + + def mifare_classic_read_block( + self, block_number: int + ) -> Optional[Union[bytes, bytearray]]: + """Read a block of data from the card. Block number should be the block + to read. If the block is successfully read a bytearray of length 16 with + data starting at the specified block will be returned. If the block is + not read then None will be returned. + """ + # Send InDataExchange request to read block of MiFare data. + response = self.call_function( + _COMMAND_INDATAEXCHANGE, + params=[0x01, MIFARE_CMD_READ, block_number & 0xFF], + response_length=17, + ) + # Check first response is 0x00 to show success. + if response[0] != 0x00: + return None + # Return first 4 bytes since 16 bytes are always returned. + return response[1:] + + def mifare_classic_write_block( + self, block_number: int, data: ReadableBuffer + ) -> bool: + """Write a block of data to the card. Block number should be the block + to write and data should be a byte array of length 16 with the data to + write. If the data is successfully written then True is returned, + otherwise False is returned. + """ + assert ( + data is not None and len(data) == 16 + ), "Data must be an array of 16 bytes!" + # Build parameters for InDataExchange command to do MiFare classic write. + params = bytearray(19) + params[0] = 0x01 # Max card numbers + params[1] = MIFARE_CMD_WRITE + params[2] = block_number & 0xFF + params[3:] = data + # Send InDataExchange request. + response = self.call_function( + _COMMAND_INDATAEXCHANGE, params=params, response_length=1 + ) + return response[0] == 0x0 + + def mifare_classic_sub_value_block(self, block_number: int, amount: int) -> bool: + """Decrease the balance of a value block. Block number should be the block + to change and amount should be an integer up to a maximum of 2147483647. + If the value block is successfully updated then True is returned, + otherwise False is returned. + """ + params = [0x01, MIFARE_CMD_DECREMENT, block_number & 0xFF] + params.extend(list(amount.to_bytes(4, "little"))) + + response = self.call_function( + _COMMAND_INDATAEXCHANGE, params=params, response_length=1 + ) + if response[0] != 0x00: + return False + + response = self.call_function( + _COMMAND_INDATAEXCHANGE, + params=[0x01, MIFARE_CMD_TRANSFER, block_number & 0xFF], + response_length=1, + ) + + return response[0] == 0x00 + + def mifare_classic_add_value_block(self, block_number: int, amount: int) -> bool: + """Increase the balance of a value block. Block number should be the block + to change and amount should be an integer up to a maximum of 2147483647. + If the value block is successfully updated then True is returned, + otherwise False is returned. + """ + params = [0x01, MIFARE_CMD_INCREMENT, block_number & 0xFF] + params.extend(list(amount.to_bytes(4, "little"))) + + response = self.call_function( + _COMMAND_INDATAEXCHANGE, params=params, response_length=1 + ) + if response[0] != 0x00: + return False + + response = self.call_function( + _COMMAND_INDATAEXCHANGE, + params=[0x01, MIFARE_CMD_TRANSFER, block_number & 0xFF], + response_length=1, + ) + + return response[0] == 0x00 + + def mifare_classic_get_value_block(self, block_number: int) -> int: + """Read the contents of a value block and return a integer representing the + current balance. Block number should be the block to read. + """ + block = self.mifare_classic_read_block(block_number=block_number) + if block is None: + return None + + value = block[0:4] + value_inverted = block[4:8] + value_backup = block[8:12] + if value != value_backup: + raise RuntimeError( + "Value block bytes 0-3 do not match 8-11: " + + "".join("%02x" % b for b in block) + ) + if value_inverted != bytearray(map((lambda x: x ^ 0xFF), value)): + raise RuntimeError( + "Inverted value block bytes 4-7 not valid: " + + "".join("%02x" % b for b in block) + ) + + return struct.unpack(" bool: + """Formats a block on the card so it is suitable for use as a value block. + Block number should be the block to use. Initial value should be an integer + up to a maximum of 2147483647. Address block is optional and can be used + as part of backup management. + """ + data = bytearray() + initial_value = initial_value.to_bytes(4, "little") + # Value + data.extend(initial_value) + # Inverted value + data.extend(bytearray(map((lambda x: x ^ 0xFF), initial_value))) + # Duplicate of value + data.extend(initial_value) + + # Address + address_block = address_block.to_bytes(1, "little")[0] + data.extend( + [address_block, address_block ^ 0xFF, address_block, address_block ^ 0xFF] + ) + + return self.mifare_classic_write_block(block_number, data) + + def ntag2xx_write_block(self, block_number: int, data: ReadableBuffer) -> bool: + """Write a block of data to the card. Block number should be the block + to write and data should be a byte array of length 4 with the data to + write. If the data is successfully written then True is returned, + otherwise False is returned. + """ + assert data is not None and len(data) == 4, "Data must be an array of 4 bytes!" + # Build parameters for InDataExchange command to do NTAG203 classic write. + params = bytearray(3 + len(data)) + params[0] = 0x01 # Max card numbers + params[1] = MIFARE_ULTRALIGHT_CMD_WRITE + params[2] = block_number & 0xFF + params[3:] = data + # Send InDataExchange request. + response = self.call_function( + _COMMAND_INDATAEXCHANGE, params=params, response_length=1 + ) + return response[0] == 0x00 + + def ntag2xx_read_block( + self, block_number: int + ) -> Optional[Union[bytes, bytearray]]: + """Read a block of data from the card. Block number should be the block + to read. If the block is successfully read the first 4 bytes (after the + leading 0x00 byte) will be returned. + If the block is not read then None will be returned. + """ + ntag2xx_block = self.mifare_classic_read_block(block_number) + if ntag2xx_block is not None: + return ntag2xx_block[0:4] # only 4 bytes per page + return None diff --git a/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/i2c.py b/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/i2c.py new file mode 100644 index 0000000000000000000000000000000000000000..c6b81f74e1eb7588d7560398b40fd9d133ac3d39 --- /dev/null +++ b/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/i2c.py @@ -0,0 +1,144 @@ +# SPDX-FileCopyrightText: 2015-2018 Tony DiCola for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +``adafruit_pn532.i2c`` +==================================================== + +This module will let you communicate with a PN532 RFID/NFC shield or breakout +using I2C. + +* Author(s): Original Raspberry Pi code by Tony DiCola, CircuitPython by ladyada, + refactor by Carter Nelson + +""" + +__version__ = "2.4.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PN532.git" + +import time +from adafruit_bus_device import i2c_device +from digitalio import Direction +from micropython import const +from adafruit_pn532.adafruit_pn532 import PN532, BusyError + +try: + from typing import Optional + from digitalio import DigitalInOut # pylint: disable=ungrouped-imports + from busio import I2C +except ImportError: + pass + +_I2C_ADDRESS = const(0x24) + + +class PN532_I2C(PN532): + """Driver for the PN532 connected over I2C.""" + + def __init__( + self, + i2c: I2C, + address: int = _I2C_ADDRESS, + *, + irq: Optional[DigitalInOut] = None, + reset: Optional[DigitalInOut] = None, + req: Optional[DigitalInOut] = None, + debug: bool = False + ) -> None: + """Create an instance of the PN532 class using I2C. Note that PN532 + uses clock stretching. Optional IRQ pin (not used), + resetp pin and debugging output. + + :param ~busio.I2C i2c: The I2C bus the PN532 is connected to. + :param int address: The I2C device address. Defaults to :const:`0x24` + :param digitalio.DigitalInOut irq: board pin the PN532 IRQ is connected to + :param digitalio.DigitalInOut reset: board pin the PN532 RSTOUT_N is connected to + :param digitalio.DigitalInOut req: board pin the PN532 P32 is connected to + :param bool debug: if True print additional debug statements. Defaults to False + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`PN532_I2C` class. + First you will need to import the libraries to use the sensor + + .. code-block:: python + + import board + import busio + from digitalio import DigitalInOut + from adafruit_pn532.i2c import PN532_I2C + + Once this is done you can define your `board.I2C` object and define your object + + .. code-block:: python + + i2c = busio.I2C(board.SCL, board.SDA) + reset_pin = DigitalInOut(board.D6) + # On Raspberry Pi, you must also connect a pin to P32 "H_Request" for hardware + # wakeup! this means we don't need to do the I2C clock-stretch thing + req_pin = DigitalInOut(board.D12) + pn532 = PN532_I2C(i2c, debug=False, reset=reset_pin, req=req_pin) + # Configure PN532 to communicate with MiFare cards + pn532.SAM_configuration() + + Now you have access to the attributes and functions of the PN532 RFID/NFC + shield or breakout + + .. code-block:: python + + uid = pn532.read_passive_target(timeout=0.5) + + """ + self.debug = debug + self._req = req + self._i2c = i2c_device.I2CDevice(i2c, address) + super().__init__(debug=debug, irq=irq, reset=reset) + + def _wakeup(self) -> None: + """Send any special commands/data to wake up PN532""" + if self._reset_pin: + self._reset_pin.value = True + time.sleep(0.01) + if self._req: + self._req.direction = Direction.OUTPUT + self._req.value = False + time.sleep(0.01) + self._req.value = True + time.sleep(0.01) + self.low_power = False + self.SAM_configuration() # Put the PN532 back in normal mode + + def _wait_ready(self, timeout: float = 1) -> bool: + """Poll PN532 if status byte is ready, up to `timeout` seconds""" + status = bytearray(1) + timestamp = time.monotonic() + while (time.monotonic() - timestamp) < timeout: + try: + with self._i2c: + self._i2c.readinto(status) + except OSError: + continue + if status == b"\x01": + return True # No longer busy + time.sleep(0.01) # let's ask again soon! + # Timed out! + return False + + def _read_data(self, count: int) -> bytearray: + """Read a specified count of bytes from the PN532.""" + # Build a read request frame. + frame = bytearray(count + 1) + with self._i2c as i2c: + i2c.readinto(frame, end=1) # read status byte! + if frame[0] != 0x01: # not ready + raise BusyError + i2c.readinto(frame) # ok get the data, plus statusbyte + if self.debug: + print("Reading: ", [hex(i) for i in frame[1:]]) + return frame[1:] # don't return the status byte + + def _write_data(self, framebytes: bytes) -> None: + """Write a specified count of bytes to the PN532""" + with self._i2c as i2c: + i2c.write(framebytes) diff --git a/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/spi.py b/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/spi.py new file mode 100644 index 0000000000000000000000000000000000000000..3ece39949c68b3e00a037dacb9a387ea76cf9827 --- /dev/null +++ b/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/spi.py @@ -0,0 +1,155 @@ +# SPDX-FileCopyrightText: 2015-2018 Tony DiCola for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +``adafruit_pn532.spi`` +==================================================== + +This module will let you communicate with a PN532 RFID/NFC shield or breakout +using SPI. + +* Author(s): Original Raspberry Pi code by Tony DiCola, CircuitPython by ladyada, + refactor by Carter Nelson + +""" + +try: + from typing import Optional + from circuitpython_typing import ReadableBuffer + from digitalio import DigitalInOut + from busio import SPI +except ImportError: + pass + +__version__ = "2.4.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PN532.git" + +import time +from adafruit_bus_device import spi_device +from micropython import const +from adafruit_pn532.adafruit_pn532 import PN532 + +_SPI_STATREAD = const(0x02) +_SPI_DATAWRITE = const(0x01) +_SPI_DATAREAD = const(0x03) +_SPI_READY = const(0x01) + + +def reverse_bit(num: int) -> int: + """Turn an LSB byte to an MSB byte, and vice versa. Used for SPI as + it is LSB for the PN532, but 99% of SPI implementations are MSB only!""" + result = 0 + for _ in range(8): + result <<= 1 + result += num & 1 + num >>= 1 + return result + + +class PN532_SPI(PN532): + """Driver for the PN532 connected over SPI. Pass in a hardware or bitbang + SPI device & chip select digitalInOut pin. Optional IRQ pin (not used), + reset pin and debugging output.""" + + def __init__( + self, + spi: SPI, + cs_pin: DigitalInOut, + *, + irq: Optional[DigitalInOut] = None, + reset: Optional[DigitalInOut] = None, + debug: bool = False + ) -> None: + """Create an instance of the PN532 class using SPI + Optional IRQ pin (not used) + + :param ~busio.SPI spi: The spi bus the PN532 is connected to. + :param digitalio.DigitalInOut cs: board pin the PN532 chip select line is connected to + :param digitalio.DigitalInOut irq: board pin the PN532 P32 is connected to + :param digitalio.DigitalInOut reset: board pin the PN532 RSTOUT_N is connected to + :param bool debug: if True print additional debug statements. Defaults to False + + + **Quickstart: Importing and using the device** + Here is an example of using the :class:`PN532_SPI` class. + First you will need to import the libraries to use the sensor + + .. code-block:: python + + import board + import busio + from digitalio import DigitalInOut + from adafruit_pn532.spi import PN532_SPI + + Once this is done you can define your `busio.SPI` object and define your PN532 object + + .. code-block:: python + + spi = busio.SPI(board.SCK, board.MOSI, board.MISO) + cs_pin = DigitalInOut(board.D5) + pn532 = PN532_SPI(spi, cs_pin, debug=False) + + Now you have access to the attributes and functions of the PN532 RFID/NFC + shield or breakout + + .. code-block:: python + + uid = pn532.read_passive_target(timeout=0.5) + + """ + self.debug = debug + self._spi = spi_device.SPIDevice(spi, cs_pin) + super().__init__(debug=debug, irq=irq, reset=reset) + + def _wakeup(self) -> None: + """Send any special commands/data to wake up PN532""" + if self._reset_pin: + self._reset_pin.value = True + time.sleep(0.01) + with self._spi as spi: + spi.write(bytearray([0x00])) # pylint: disable=no-member + time.sleep(0.01) + self.low_power = False + self.SAM_configuration() # Put the PN532 back in normal mode + + def _wait_ready(self, timeout: float = 1) -> bool: + """Poll PN532 if status byte is ready, up to `timeout` seconds""" + status_cmd = bytearray([reverse_bit(_SPI_STATREAD), 0x00]) + status_response = bytearray([0x00, 0x00]) + timestamp = time.monotonic() + with self._spi as spi: + while (time.monotonic() - timestamp) < timeout: + spi.write_readinto( + status_cmd, status_response + ) # pylint: disable=no-member + if reverse_bit(status_response[1]) == 0x01: # LSB data is read in MSB + return True # Not busy anymore! + time.sleep(0.01) # pause a bit till we ask again + # We timed out! + return False + + def _read_data(self, count: int) -> bytearray: + """Read a specified count of bytes from the PN532.""" + # Build a read request frame. + frame = bytearray(count + 1) + # Add the SPI data read signal byte, but LSB'ify it + frame[0] = reverse_bit(_SPI_DATAREAD) + + with self._spi as spi: + spi.write_readinto(frame, frame) # pylint: disable=no-member + for i, val in enumerate(frame): + frame[i] = reverse_bit(val) # turn LSB data to MSB + if self.debug: + print("Reading: ", [hex(i) for i in frame[1:]]) + return frame[1:] + + def _write_data(self, framebytes: ReadableBuffer) -> None: + """Write a specified count of bytes to the PN532""" + # start by making a frame with data write in front, + # then rest of bytes, and LSBify it + rev_frame = [reverse_bit(x) for x in bytes([_SPI_DATAWRITE]) + framebytes] + if self.debug: + print("Writing: ", [hex(i) for i in rev_frame]) + with self._spi as spi: + spi.write(bytes(rev_frame)) # pylint: disable=no-member diff --git a/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/uart.py b/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/uart.py new file mode 100644 index 0000000000000000000000000000000000000000..f83462a17d736a3dde115da86ab5c86c0849605e --- /dev/null +++ b/rpi_pico_fw/circuitpython/fw/lib/adafruit_pn532/uart.py @@ -0,0 +1,109 @@ +# SPDX-FileCopyrightText: 2015-2018 Tony DiCola for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +``adafruit_pn532.uart`` +==================================================== + +This module will let you communicate with a PN532 RFID/NFC shield or breakout +using UART. + +* Author(s): Original Raspberry Pi code by Tony DiCola, CircuitPython by ladyada, + refactor by Carter Nelson + +""" + +__version__ = "2.4.1" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PN532.git" + +try: + from typing import Optional + from circuitpython_typing import ReadableBuffer + from digitalio import DigitalInOut + from busio import UART +except ImportError: + pass + +import time +from adafruit_pn532.adafruit_pn532 import PN532, BusyError + + +class PN532_UART(PN532): + """Driver for the PN532 connected over Serial UART""" + + def __init__( + self, uart: UART, *, reset: Optional[DigitalInOut] = None, debug: bool = False + ) -> None: + """Create an instance of the PN532 class using Serial connection. + Optional reset pin and debugging output. + + :param ~busio.UART uart: The uart object the PN532 is connected to. + :param digitalio.DigitalInOut reset: board pin the PN532 RSTOUT_N is connected to + :param bool debug: if True print additional debug statements. Defaults to False + + **Quickstart: Importing and using the device** + + Here is an example of using the :class:`PN532_I2C` class. + First you will need to import the libraries to use the sensor + + .. code-block:: python + + import board + import busio + from digitalio import DigitalInOut + from adafruit_pn532.uart import PN532_UART + + Once this is done you can define your `busio.UART` object and define your PN532 object + + .. code-block:: python + + uart = busio.UART(board.TX, board.RX, baudrate=115200, timeout=0.1) + pn532 = PN532_UART(uart, debug=False) + + Now you have access to the attributes and functions of the PN532 RFID/NFC + shield or breakout + + .. code-block:: python + + uid = pn532.read_passive_target(timeout=0.5) + + """ + self.debug = debug + self._uart = uart + super().__init__(debug=debug, reset=reset) + + def _wakeup(self) -> None: + """Send any special commands/data to wake up PN532""" + if self._reset_pin: + self._reset_pin.value = True + time.sleep(0.01) + self.low_power = False + self._uart.write( + b"\x55\x55\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) # wake up! + self.SAM_configuration() + + def _wait_ready(self, timeout: float = 1) -> bool: + """Wait `timeout` seconds""" + timestamp = time.monotonic() + while (time.monotonic() - timestamp) < timeout: + if self._uart.in_waiting > 0: + return True # No Longer Busy + time.sleep(0.01) # lets ask again soon! + # Timed out! + return False + + def _read_data(self, count: int) -> bytes: + """Read a specified count of bytes from the PN532.""" + frame = self._uart.read(count) + if not frame: + raise BusyError("No data read from PN532") + if self.debug: + print("Reading: ", [hex(i) for i in frame]) + return frame + + def _write_data(self, framebytes: ReadableBuffer) -> None: + """Write a specified count of bytes to the PN532""" + self._uart.reset_input_buffer() + self._uart.write(framebytes) diff --git a/rpi_pico_fw/circuitpython/fw/lib/adafruit_ticks.mpy b/rpi_pico_fw/circuitpython/fw/lib/adafruit_ticks.mpy new file mode 100644 index 0000000000000000000000000000000000000000..845fee8932ef2b9979b9ad9b691b81bf9a311edf Binary files /dev/null and b/rpi_pico_fw/circuitpython/fw/lib/adafruit_ticks.mpy differ diff --git a/rpi_pico_fw/circuitpython/fw/lib/asyncio/core.mpy b/rpi_pico_fw/circuitpython/fw/lib/asyncio/core.mpy new file mode 100644 index 0000000000000000000000000000000000000000..3aa74ded9972b12a6d3c3f6f57fc28fa50bf733b Binary files /dev/null and b/rpi_pico_fw/circuitpython/fw/lib/asyncio/core.mpy differ diff --git a/rpi_pico_fw/circuitpython/fw/lib/asyncio/event.mpy b/rpi_pico_fw/circuitpython/fw/lib/asyncio/event.mpy new file mode 100644 index 0000000000000000000000000000000000000000..bd068a61bfb334ee33ac93f4fc82a451fe382b71 Binary files /dev/null and b/rpi_pico_fw/circuitpython/fw/lib/asyncio/event.mpy differ diff --git a/rpi_pico_fw/circuitpython/fw/lib/asyncio/funcs.mpy b/rpi_pico_fw/circuitpython/fw/lib/asyncio/funcs.mpy new file mode 100644 index 0000000000000000000000000000000000000000..7eb1d6e1cf33ed7b3062428bdd58ece35e94d811 Binary files /dev/null and b/rpi_pico_fw/circuitpython/fw/lib/asyncio/funcs.mpy differ diff --git a/rpi_pico_fw/circuitpython/fw/lib/asyncio/lock.mpy b/rpi_pico_fw/circuitpython/fw/lib/asyncio/lock.mpy new file mode 100644 index 0000000000000000000000000000000000000000..4ff99e02c2b65de7837490ef2355328a97e479d4 Binary files /dev/null and b/rpi_pico_fw/circuitpython/fw/lib/asyncio/lock.mpy differ diff --git a/rpi_pico_fw/circuitpython/fw/lib/asyncio/manifest.mpy b/rpi_pico_fw/circuitpython/fw/lib/asyncio/manifest.mpy new file mode 100644 index 0000000000000000000000000000000000000000..5ad5b9927a98a36c3ed1054aa720c2b109f3758b Binary files /dev/null and b/rpi_pico_fw/circuitpython/fw/lib/asyncio/manifest.mpy differ diff --git a/rpi_pico_fw/circuitpython/fw/lib/asyncio/stream.mpy b/rpi_pico_fw/circuitpython/fw/lib/asyncio/stream.mpy new file mode 100644 index 0000000000000000000000000000000000000000..94719efe9528c0ca7077c74babe84c5801986c0e Binary files /dev/null and b/rpi_pico_fw/circuitpython/fw/lib/asyncio/stream.mpy differ diff --git a/rpi_pico_fw/circuitpython/fw/lib/asyncio/task.mpy b/rpi_pico_fw/circuitpython/fw/lib/asyncio/task.mpy new file mode 100644 index 0000000000000000000000000000000000000000..583bdcfc5dae084a3b2821933aa13cf7c1014e34 Binary files /dev/null and b/rpi_pico_fw/circuitpython/fw/lib/asyncio/task.mpy differ diff --git a/rpi_pico_fw/circuitpython/fw/lib/asyncio/traceback.mpy b/rpi_pico_fw/circuitpython/fw/lib/asyncio/traceback.mpy new file mode 100644 index 0000000000000000000000000000000000000000..d31773481ffc8ecfcf1e0794c86718921330e65c Binary files /dev/null and b/rpi_pico_fw/circuitpython/fw/lib/asyncio/traceback.mpy differ