├── .github └── workflows │ └── build.yml ├── LICENSE ├── README.md ├── blinky ├── README.md ├── go.mod ├── go.sum ├── main.go └── modules │ ├── blink.wasm │ └── blink │ ├── README.md │ ├── blink.json │ ├── go.mod │ ├── go.sum │ └── main.go ├── buttons ├── README.md ├── devices │ └── display │ │ └── display.go ├── go.mod ├── go.sum ├── main.go └── modules │ ├── buttons.wasm │ └── buttons │ ├── README.md │ ├── buttons.json │ ├── go.mod │ ├── go.sum │ └── main.go ├── display ├── README.md ├── devices │ └── display │ │ └── display.go ├── go.mod ├── go.sum ├── main.go └── modules │ ├── ping.wasm │ └── ping │ ├── README.md │ ├── go.mod │ ├── main.go │ └── ping.json ├── externref ├── README.md ├── go.mod ├── go.sum ├── main.go └── modules │ ├── hollaback.wasm │ └── hollaback │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── hollaback.json │ └── main.go ├── filestore ├── README.md ├── baremetal.go ├── cli.go ├── go.mod ├── go.sum ├── main.go ├── modules │ ├── ping.wasm │ ├── ping │ │ ├── LICENSE │ │ ├── README.md │ │ ├── go.mod │ │ ├── main.go │ │ └── ping.json │ ├── pingrs.wasm │ ├── pingrs │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── lib.rs │ ├── pingzig.wasm │ └── pingzig │ │ ├── .gitignore │ │ ├── README.md │ │ ├── build.zig │ │ └── src │ │ └── ping.zig ├── os.go └── savefile │ └── main.go ├── images ├── blinky-pybadge.jpg ├── buttons-gopher-badge.jpg ├── buttons-pybadge.jpg ├── display-pybadge.jpg ├── thumby.jpg ├── wasmbadge-pybadge.jpg └── wasmdrone-pybadge-tello.jpg ├── simple ├── README.md ├── go.mod ├── go.sum ├── main.go └── modules │ ├── ping.wasm │ └── ping │ ├── README.md │ ├── go.mod │ ├── main.go │ └── ping.json ├── thumby ├── README.md ├── devices │ └── display │ │ └── display.go ├── go.mod ├── go.sum ├── main.go └── modules │ ├── ping.wasm │ └── ping │ ├── README.md │ ├── go.mod │ ├── main.go │ └── ping.json ├── thumbyfile ├── README.md ├── baremetal.go ├── cli.go ├── devices │ └── display │ │ └── display.go ├── go.mod ├── go.sum ├── main.go ├── modules │ ├── ping.wasm │ ├── ping │ │ ├── LICENSE │ │ ├── README.md │ │ ├── go.mod │ │ ├── main.go │ │ └── ping.json │ ├── pingrs.wasm │ ├── pingrs │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── lib.rs │ ├── pingzig.wasm │ └── pingzig │ │ ├── .gitignore │ │ ├── README.md │ │ ├── build.zig │ │ └── src │ │ └── ping.zig ├── os.go └── savefile │ └── main.go ├── wasmbadge ├── README.md ├── devices │ ├── badge │ │ ├── badge.go │ │ └── bigtext.go │ └── display │ │ └── display.go ├── go.mod ├── go.sum ├── home.go ├── main.go ├── modules │ ├── hithere.wasm │ ├── hithere │ │ ├── README.md │ │ ├── badge.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── hithere.json │ │ └── main.go │ ├── mynameis.wasm │ ├── mynameis │ │ ├── README.md │ │ ├── badge.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── main.go │ │ └── mynameis.json │ ├── mythingis.wasm │ ├── mythingis │ │ ├── README.md │ │ ├── badge.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── main.go │ │ └── mythingis.json │ ├── thisbadge.wasm │ ├── thisbadge │ │ ├── README.md │ │ ├── badge.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── main.go │ │ └── thisbadge.json │ ├── youarehere.wasm │ └── youarehere │ │ ├── README.md │ │ ├── badge.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── main.go │ │ └── youarehere.json └── runner.go └── wasmdrone ├── README.md ├── devices ├── display │ └── display.go └── drone │ └── drone.go ├── go.mod ├── go.sum ├── main.go └── modules ├── drone.wasm └── drone ├── LICENSE ├── README.md ├── display.go ├── drone.go ├── drone.json ├── go.mod ├── go.sum └── main.go /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - dev 8 | - release 9 | workflow_dispatch: 10 | 11 | jobs: 12 | smoketest: 13 | strategy: 14 | matrix: 15 | project: [ 16 | 'blinky', 17 | 'buttons', 18 | 'display', 19 | 'externref', 20 | 'filestore', 21 | 'simple', 22 | 'thumby', 23 | 'wasmbadge', 24 | 'wasmdrone'] 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout code 28 | uses: actions/checkout@v3 29 | - name: Setup Go 30 | uses: actions/setup-go@v5 31 | with: 32 | go-version: '1.22' 33 | - uses: acifani/setup-tinygo@v2 34 | with: 35 | tinygo-version: '0.31.2' 36 | - name: TinyGo version check 37 | run: tinygo version 38 | - name: Setup Rust 39 | uses: dtolnay/rust-toolchain@stable 40 | with: 41 | targets: wasm32-unknown-unknown 42 | - name: Rust version check 43 | run: rustc --version 44 | - name: Setup Zig 45 | uses: goto-bus-stop/setup-zig@v2 46 | with: 47 | version: 0.11.0 48 | - name: Zig version check 49 | run: zig version 50 | - name: Install gonew 51 | run: go install golang.org/x/tools/cmd/gonew@latest 52 | - name: Install Mechanoid CLI 53 | run: go install github.com/hybridgroup/mechanoid/cmd/mecha@latest 54 | - name: Mechanoid version check 55 | run: mecha -v 56 | - name: Build project modules 57 | run: mecha build 58 | working-directory: ${{ matrix.project }} 59 | - name: Default target for smoke test 60 | run: | 61 | echo "BUILD_TARGET=pybadge" >> $GITHUB_ENV 62 | - name: Handle additional target for smoke test 63 | run: | 64 | echo "BUILD_TARGET=thumby" >> $GITHUB_ENV 65 | if: ${{ matrix.project == 'thumby' }} 66 | - name: Build project binary 67 | run: tinygo build -o ${{ matrix.project }}.uf2 -target ${{env.BUILD_TARGET}} . 68 | working-directory: ${{ matrix.project }} 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) The Hybrid Group 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | --- LLVM Exceptions to the Apache 2.0 License ---- 205 | 206 | As an exception, if, as a result of your compiling your source code, portions 207 | of this Software are embedded into an Object form of such source code, you 208 | may redistribute such embedded portions in such Object form without complying 209 | with the conditions of Sections 4(a), 4(b) and 4(d) of the License. 210 | 211 | In addition, if you combine or link compiled forms of this Software with 212 | software that is licensed under the GPLv2 ("Combined Software") and if a 213 | court of competent jurisdiction determines that the patent provision (Section 214 | 3), the indemnity provision (Section 9) or other Section of the License 215 | conflicts with the conditions of the GPLv2, you may retroactively and 216 | prospectively choose to deem waived or otherwise exclude such Section(s) of 217 | the License, but only in their entirety and only with respect to the Combined 218 | Software. 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Mechanoid logo](https://mechanoid.io/images/logo-blue.png) 2 | 3 | # Mechanoid Examples 4 | 5 | This repo contains example applications written using Mechanoid (https://mechanoid.io) 6 | 7 | See the README files inside each for details on what they do, how they work and the **recommended runtime interpreter** (wazero, wasman,...), as there are some differences among them and the example might not run correctly. 8 | 9 | ## Blinky 10 | 11 | ![Blinky](./images/blinky-pybadge.jpg) 12 | 13 | Application that loads an embedded WASM program that blinks an LED on the hardware. 14 | 15 | ## Buttons 16 | 17 | ![Buttons](./images/buttons-gopher-badge.jpg) 18 | 19 | Application that loads an embedded WASM program and sends it events from pressing the buttons. The WASM programs then displays messages on the small screen on the hardware device. 20 | 21 | ## Display 22 | 23 | ![Display](./images/display-pybadge.jpg) 24 | 25 | Application that loads an embedded WASM program and displays the interaction on the small screen on the hardware device. 26 | 27 | ## Externref 28 | 29 | Application built using Mechanoid that uses host external references. 30 | 31 | ## Filestore 32 | 33 | Application that demonstrates how to use the onboard Flash storage on the hardware device to save/load/run external WASM modules via a Command line interface directly on the device itself. 34 | 35 | Also shows how to use Mechanoid with WASM modules written using TinyGo, Rust, or Zig. 36 | 37 | ## Simple 38 | 39 | Example of a simple "ping" application built using Mechanoid. 40 | 41 | ## Thumby 42 | 43 | ![Thumby](./images/thumby.jpg) 44 | 45 | This is an example of an application built using Mechanoid specifically for the Thumby "itty-bitty game system". 46 | 47 | ## ThumbyFile 48 | 49 | ![Thumby](./images/thumby.jpg) 50 | 51 | Application that demonstrates how to use the onboard Flash storage on the Thumby device to save/load/run external WASM modules via a Command line interface directly on the device itself, along with display support so you can see what it happening on the tiny display. 52 | 53 | Also shows how to use Mechanoid with WASM modules written using TinyGo, Rust, or Zig. 54 | 55 | ## WASMBadge 56 | 57 | ![WASMBadge](./images/wasmbadge-pybadge.jpg) 58 | 59 | This application is a conference badge programmed using WASM. 60 | 61 | ## WASMDrone 62 | 63 | ![WASMDrone](./images/wasmdrone-pybadge-tello.jpg) 64 | 65 | This application lets you write a WebAssembly program that runs on a hardware device with connected wireless to fly a DJI Tello drone. 66 | -------------------------------------------------------------------------------- /blinky/README.md: -------------------------------------------------------------------------------- 1 | # Blinky 2 | 3 | ![Blinky](../images/blinky-pybadge.jpg) 4 | 5 | Application that loads an embedded WASM program that blinks an LED on the hardware. 6 | 7 | ## How to run 8 | 9 | ### Pybadge 10 | 11 | ``` 12 | $ mecha flash -i wazero -m pybadge 13 | Building module blink 14 | Done. 15 | code data bss | flash ram 16 | 57 0 0 | 57 0 17 | Application built. Now flashing... 18 | code data bss | flash ram 19 | 331780 66260 7112 | 398040 73372 20 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 21 | Mechanoid engine starting... 22 | Using interpreter wazero 23 | Initializing engine... 24 | Initializing interpreter... 25 | Initializing devices... 26 | Loading module... 27 | Running module... 28 | Calling setup... 29 | Calling loop... 30 | Calling loop... 31 | Calling loop... 32 | ... 33 | ``` 34 | 35 | ### gopher-badge 36 | 37 | ``` 38 | $ mecha flash -m gopher-badge 39 | code data bss | flash ram 40 | 103748 2316 6680 | 106064 8996 41 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 42 | Mechanoid engine starting... 43 | Using interpreter... 44 | Initializing engine... 45 | Loading module... 46 | Running module... 47 | Calling setup... 48 | Calling loop... 49 | Calling loop... 50 | Calling loop... 51 | ``` 52 | -------------------------------------------------------------------------------- /blinky/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/blinky 2 | 3 | go 1.22.0 4 | 5 | require github.com/hybridgroup/mechanoid v0.2.0 6 | 7 | require ( 8 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 9 | github.com/orsinium-labs/wypes v0.1.4 // indirect 10 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /blinky/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.2.0 h1:2Kg3c0cK9JK8ZeJkiN+l2o4B0QJYnNHkNEKraMFOugQ= 2 | github.com/hybridgroup/mechanoid v0.2.0/go.mod h1:7jQEJ0blXXbIcqmu2/d7vSxWL5Dxbt18zZirJ35OeXc= 3 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 h1:pP916/7OLromi+7fHcbw5jIXOT/idiEBQ3ucbl8caz8= 4 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834/go.mod h1:rLavUo4P0xVcDeDnViYEpPQPoACmp1py9UTLPY/R7Lg= 5 | github.com/orsinium-labs/tinytest v1.0.0 h1:YiGm/whlGm3mn/ynx9CCFuvEa3Q6yEGrzrKXEqJOkdc= 6 | github.com/orsinium-labs/tinytest v1.0.0/go.mod h1:GwcYBp0aKi6zujzBXFpCnqw6RSLSp9JSedDyu/V1DF4= 7 | github.com/orsinium-labs/wypes v0.1.4 h1:+7oih2IDlEpz7laiL3sQFlIU8vjd5j/SwcYlPdIPc0Q= 8 | github.com/orsinium-labs/wypes v0.1.4/go.mod h1:FSNWGo8I6/D5RYXMkCxyu71TXJAlwJGQUxgs4i6MAwo= 9 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 h1:sB7EW6YA2z8k4or+w8Tm2eo1wD/f4uQ31AffFfgRhvo= 10 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 11 | -------------------------------------------------------------------------------- /blinky/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "time" 7 | 8 | "github.com/hybridgroup/mechanoid/devices/hardware" 9 | "github.com/hybridgroup/mechanoid/engine" 10 | "github.com/hybridgroup/mechanoid/interp" 11 | ) 12 | 13 | //go:embed modules/blink.wasm 14 | var wasmCode []byte 15 | 16 | func main() { 17 | time.Sleep(3 * time.Second) 18 | 19 | println("Mechanoid engine starting...") 20 | eng := engine.NewEngine() 21 | eng.UseInterpreter(interp.NewInterpreter()) 22 | eng.AddDevice(hardware.GPIO{}) 23 | 24 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 25 | if err := eng.Init(); err != nil { 26 | println(err.Error()) 27 | return 28 | } 29 | 30 | println("Loading and running WASM code...") 31 | ins, err := eng.LoadAndRun(bytes.NewReader(wasmCode)) 32 | if err != nil { 33 | println(err.Error()) 34 | return 35 | } 36 | 37 | println("Calling setup...") 38 | if _, err := ins.Call("setup"); err != nil { 39 | println(err.Error()) 40 | } 41 | 42 | for { 43 | println("Calling loop...") 44 | if _, err := ins.Call("loop"); err != nil { 45 | println(err.Error()) 46 | } 47 | 48 | time.Sleep(1 * time.Second) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /blinky/modules/blink.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/blinky/modules/blink.wasm -------------------------------------------------------------------------------- /blinky/modules/blink/README.md: -------------------------------------------------------------------------------- 1 | # Blink 2 | 3 | Mechanoid WebAssembly module that blinks a host LED. 4 | -------------------------------------------------------------------------------- /blinky/modules/blink/blink.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mnontrapping-fptoint", 15 | "-msign-ext" 16 | ], 17 | "ldflags": [ 18 | "--stack-first", 19 | "--no-demangle", 20 | "--no-entry", 21 | "--initial-memory=65536", 22 | "--max-memory=65536", 23 | "-zstack-size=4096" 24 | ], 25 | "extra-files": [ 26 | "src/runtime/asm_tinygowasm.S" 27 | ], 28 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 29 | } 30 | -------------------------------------------------------------------------------- /blinky/modules/blink/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/blinky/modules/blink 2 | 3 | go 1.22.0 4 | -------------------------------------------------------------------------------- /blinky/modules/blink/go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/blinky/modules/blink/go.sum -------------------------------------------------------------------------------- /blinky/modules/blink/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | ) 8 | 9 | var ( 10 | led = machine.Pin(23) 11 | on bool 12 | ) 13 | 14 | //go:export setup 15 | func setup() { 16 | led.Configure(machine.PinConfig{Mode: machine.PinOutput}) 17 | } 18 | 19 | //go:export loop 20 | func loop() { 21 | on = !on 22 | led.Set(on) 23 | on = led.Get() 24 | } 25 | 26 | func main() {} 27 | -------------------------------------------------------------------------------- /buttons/README.md: -------------------------------------------------------------------------------- 1 | # Buttons 2 | 3 | Application that loads an embedded WASM program and sends it events from pressing the buttons. The WASM programs then displays messages on the small screen on the hardware device. 4 | 5 | ### PyBadge 6 | 7 | ![Buttons](../images/buttons-pybadge.jpg) 8 | 9 | ```bash 10 | $ mecha flash -i wazero pybadge 11 | Building module buttons 12 | Done. 13 | code data bss | flash ram 14 | 102 5 65531 | 107 65536 15 | Application built. Now flashing... 16 | code data bss | flash ram 17 | 342608 66300 7136 | 408908 73436 18 | ``` 19 | 20 | ### Gopher Badge 21 | 22 | ![Buttons](../images/buttons-gopher-badge.jpg) 23 | 24 | ```bash 25 | $ mecha flash -i wazero gopher-badge 26 | Building module buttons 27 | Done. 28 | code data bss | flash ram 29 | 102 5 65531 | 107 65536 30 | Application built. Now flashing... 31 | code data bss | flash ram 32 | 374480 66304 3656 | 440784 69960 33 | ``` 34 | -------------------------------------------------------------------------------- /buttons/devices/display/display.go: -------------------------------------------------------------------------------- 1 | package display 2 | 3 | import ( 4 | "github.com/aykevl/board" 5 | "github.com/aykevl/tinygl" 6 | "github.com/aykevl/tinygl/style" 7 | "github.com/aykevl/tinygl/style/basic" 8 | "github.com/orsinium-labs/wypes" 9 | "tinygo.org/x/drivers/pixel" 10 | ) 11 | 12 | var pongCount int 13 | 14 | type Device[T pixel.Color] struct { 15 | Display board.Displayer[T] 16 | Screen *tinygl.Screen[T] 17 | Theme *basic.Basic[T] 18 | VBox *tinygl.VBox[T] 19 | Header *tinygl.Text[T] 20 | MessageText *tinygl.Text[T] 21 | } 22 | 23 | // NewDevice creates a new display device. 24 | func NewDevice[T pixel.Color](display board.Displayer[T]) *Device[T] { 25 | return &Device[T]{ 26 | Display: display, 27 | } 28 | } 29 | 30 | func (d *Device[T]) Modules() wypes.Modules { 31 | return wypes.Modules{ //} 32 | "display": wypes.Module{ 33 | "message": wypes.H1(d.showMessage), 34 | }, 35 | } 36 | } 37 | 38 | func (d *Device[T]) Init() error { 39 | width, height := d.Display.Size() 40 | scalePercent := board.Display.PPI() * 100 / 120 41 | 42 | // Initialize the screen. 43 | buf := pixel.NewImage[T](int(width), int(height)/4) 44 | d.Screen = tinygl.NewScreen[T](d.Display, buf, board.Display.PPI()) 45 | d.Theme = basic.NewTheme(style.NewScale(scalePercent), d.Screen) 46 | d.Header = d.Theme.NewText("Mechanoid Buttons") 47 | d.MessageText = d.Theme.NewText("waiting...") 48 | d.VBox = d.Theme.NewVBox(d.Header, d.MessageText) 49 | 50 | d.Screen.SetChild(d.VBox) 51 | d.Screen.Update() 52 | board.Display.SetBrightness(board.Display.MaxBrightness()) 53 | 54 | return nil 55 | } 56 | 57 | func (d *Device[T]) ShowMessage(msg string) { 58 | d.MessageText.SetText(msg) 59 | d.Screen.Update() 60 | } 61 | 62 | func (d *Device[T]) showMessage(msg wypes.String) wypes.Void { 63 | d.ShowMessage(msg.Unwrap()) 64 | return wypes.Void{} 65 | } 66 | -------------------------------------------------------------------------------- /buttons/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/buttons 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/aykevl/board v0.0.0-20240106144210-80ca76f77def 7 | github.com/aykevl/tinygl v0.0.0-20240131130748-3033a2fd9182 8 | github.com/hybridgroup/mechanoid v0.2.0 9 | github.com/orsinium-labs/wypes v0.1.4 10 | tinygo.org/x/drivers v0.27.0 11 | ) 12 | 13 | require ( 14 | fyne.io/fyne/v2 v2.3.4 // indirect 15 | fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/fredbi/uri v0.1.0 // indirect 18 | github.com/fsnotify/fsnotify v1.5.4 // indirect 19 | github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect 20 | github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect 21 | github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect 22 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect 23 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect 24 | github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 // indirect 25 | github.com/godbus/dbus/v5 v5.1.0 // indirect 26 | github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect 27 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 28 | github.com/gopherjs/gopherjs v1.17.2 // indirect 29 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 30 | github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect 31 | github.com/pmezard/go-difflib v1.0.0 // indirect 32 | github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 // indirect 33 | github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect 34 | github.com/stretchr/testify v1.8.0 // indirect 35 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 36 | github.com/tevino/abool v1.2.0 // indirect 37 | github.com/yuin/goldmark v1.4.13 // indirect 38 | golang.org/x/image v0.3.0 // indirect 39 | golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect 40 | golang.org/x/net v0.7.0 // indirect 41 | golang.org/x/sys v0.5.0 // indirect 42 | golang.org/x/text v0.7.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /buttons/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "time" 7 | 8 | "github.com/aykevl/board" 9 | "github.com/hybridgroup/mechanoid-examples/buttons/devices/display" 10 | "github.com/hybridgroup/mechanoid/engine" 11 | "github.com/hybridgroup/mechanoid/interp" 12 | "tinygo.org/x/drivers/pixel" 13 | ) 14 | 15 | //go:embed modules/buttons.wasm 16 | var wasmCode []byte 17 | 18 | var ( 19 | eng *engine.Engine 20 | 21 | pingCount, pongCount int 22 | ) 23 | 24 | // main func just calls a run() func so we can infer the display type. 25 | func main() { 26 | run(board.Display.Configure()) 27 | } 28 | 29 | // run func is the main entry point for the program. 30 | func run[T pixel.Color](disp board.Displayer[T]) { 31 | time.Sleep(2 * time.Second) 32 | 33 | println("Mechanoid engine starting...") 34 | eng := engine.NewEngine() 35 | eng.UseInterpreter(interp.NewInterpreter()) 36 | eng.AddDevice(display.NewDevice(disp)) 37 | 38 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 39 | if err := eng.Init(); err != nil { 40 | println(err.Error()) 41 | return 42 | } 43 | 44 | board.Buttons.Configure() 45 | 46 | println("Loading and running WASM code...") 47 | ins, err := eng.LoadAndRun(bytes.NewReader(wasmCode)) 48 | if err != nil { 49 | println(err.Error()) 50 | return 51 | } 52 | 53 | for { 54 | board.Buttons.ReadInput() 55 | event := board.Buttons.NextEvent() 56 | if !event.Pressed() { 57 | continue 58 | } 59 | switch event.Key() { 60 | case board.KeyA: 61 | if _, err := ins.Call("button_a"); err != nil { 62 | println(err.Error()) 63 | } 64 | case board.KeyB: 65 | if _, err := ins.Call("button_b"); err != nil { 66 | println(err.Error()) 67 | } 68 | case board.KeyUp: 69 | if _, err := ins.Call("button_up"); err != nil { 70 | println(err.Error()) 71 | } 72 | case board.KeyDown: 73 | if _, err := ins.Call("button_down"); err != nil { 74 | println(err.Error()) 75 | } 76 | case board.KeyLeft: 77 | if _, err := ins.Call("button_left"); err != nil { 78 | println(err.Error()) 79 | } 80 | case board.KeyRight: 81 | if _, err := ins.Call("button_right"); err != nil { 82 | println(err.Error()) 83 | } 84 | case board.KeySelect, board.KeyEscape: 85 | if _, err := ins.Call("button_select"); err != nil { 86 | println(err.Error()) 87 | } 88 | case board.KeyStart, board.KeyEnter: 89 | if _, err := ins.Call("button_start"); err != nil { 90 | println(err.Error()) 91 | } 92 | } 93 | 94 | time.Sleep(time.Second / 30) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /buttons/modules/buttons.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/buttons/modules/buttons.wasm -------------------------------------------------------------------------------- /buttons/modules/buttons/README.md: -------------------------------------------------------------------------------- 1 | # Buttons module 2 | 3 | WebAssembly module that exports functions called by host when buttons are pushed, and then calls the host function `display.message`. 4 | -------------------------------------------------------------------------------- /buttons/modules/buttons/buttons.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mnontrapping-fptoint", 15 | "-msign-ext" 16 | ], 17 | "ldflags": [ 18 | "--stack-first", 19 | "--no-demangle", 20 | "--no-entry", 21 | "--initial-memory=65536", 22 | "--max-memory=65536", 23 | "-zstack-size=4096" 24 | ], 25 | "extra-files": [ 26 | "src/runtime/asm_tinygowasm.S" 27 | ], 28 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 29 | } 30 | -------------------------------------------------------------------------------- /buttons/modules/buttons/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/buttons/modules/buttons 2 | 3 | go 1.22.0 4 | 5 | require github.com/hybridgroup/mechanoid v0.0.0-20240309154056-a4644a70b098 6 | -------------------------------------------------------------------------------- /buttons/modules/buttons/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.0.0-20240309154056-a4644a70b098 h1:0oYP51o602rktQ446aIyxTdHng9Ph9wza9hVzw50lmI= 2 | github.com/hybridgroup/mechanoid v0.0.0-20240309154056-a4644a70b098/go.mod h1:goPLq6Qim+Hr7nGeDflbxPNqhErDAdhpUmFp44URbo8= 3 | -------------------------------------------------------------------------------- /buttons/modules/buttons/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/hybridgroup/mechanoid/convert" 7 | ) 8 | 9 | var ( 10 | // buf is a buffer that is used to pass messages to the host instance. 11 | buf [64]byte 12 | ) 13 | 14 | //go:wasmimport display message 15 | func message(ptr, sz uint32) 16 | 17 | //go:export button_select 18 | func buttonSelect() { 19 | msg := "SELECT" 20 | copy(buf[:], msg) 21 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 22 | 23 | message(ptr, sz) 24 | } 25 | 26 | //go:export button_start 27 | func buttonStart() { 28 | msg := "START" 29 | copy(buf[:], msg) 30 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 31 | 32 | message(ptr, sz) 33 | } 34 | 35 | //go:export button_a 36 | func buttonA() { 37 | msg := "A" 38 | copy(buf[:], msg) 39 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 40 | 41 | message(ptr, sz) 42 | } 43 | 44 | //go:export button_b 45 | func buttonB() { 46 | msg := "B" 47 | copy(buf[:], msg) 48 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 49 | 50 | message(ptr, sz) 51 | } 52 | 53 | //go:export button_up 54 | func buttonUp() { 55 | msg := "UP" 56 | copy(buf[:], msg) 57 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 58 | 59 | message(ptr, sz) 60 | } 61 | 62 | //go:export button_down 63 | func buttonDown() { 64 | msg := "DOWN" 65 | copy(buf[:], msg) 66 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 67 | 68 | message(ptr, sz) 69 | } 70 | 71 | //go:export button_left 72 | func buttonLeft() { 73 | msg := "LEFT" 74 | copy(buf[:], msg) 75 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 76 | 77 | message(ptr, sz) 78 | } 79 | 80 | //go:export button_right 81 | func buttonRight() { 82 | msg := "RIGHT" 83 | copy(buf[:], msg) 84 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 85 | 86 | message(ptr, sz) 87 | } 88 | 89 | func main() {} 90 | -------------------------------------------------------------------------------- /display/README.md: -------------------------------------------------------------------------------- 1 | # Display 2 | 3 | ![Display](../images/display-pybadge.jpg) 4 | 5 | Application that loads an embedded WASM program and displays the interaction on the small screen on the hardware device. 6 | 7 | ## How it works 8 | 9 | The application can connect to any of the displays supported in the `boards` package. 10 | 11 | It then loads the `ping.wasm` program which is embedded into the application itself. 12 | 13 | ## How to run 14 | 15 | ### PyBadge 16 | 17 | ``` 18 | $ mecha flash -m -i wazero pybadge 19 | Building module ping 20 | Done. 21 | code data bss | flash ram 22 | 9 0 0 | 9 0 23 | Application built. Now flashing... 24 | code data bss | flash ram 25 | 343236 66056 7128 | 409292 73184 26 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 27 | Mechanoid engine starting... 28 | Adding display device... 29 | Using interpreter wazero 30 | Initializing engine... 31 | Initializing interpreter... 32 | Initializing devices... 33 | Loading WASM module... 34 | Running module... 35 | Ping 1 36 | Pong 1 37 | Ping 2 38 | Pong 2 39 | Ping 3 40 | Pong 3 41 | ... 42 | ``` 43 | 44 | ### Gopher Badge 45 | 46 | ``` 47 | $ mecha flash -m -i wazero gopher-badge 48 | Building module ping 49 | Done. 50 | code data bss | flash ram 51 | 9 0 0 | 9 0 52 | Application built. Now flashing... 53 | code data bss | flash ram 54 | 375100 66060 3664 | 441160 69724 55 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 56 | Mechanoid engine starting... 57 | Adding display device... 58 | Using interpreter wazero 59 | Initializing engine... 60 | Initializing interpreter... 61 | Initializing devices... 62 | Loading WASM module... 63 | Running module... 64 | Ping 1 65 | Pong 1 66 | Ping 2 67 | Pong 2 68 | Ping 3 69 | Pong 3 70 | ... 71 | ... 72 | ``` 73 | -------------------------------------------------------------------------------- /display/devices/display/display.go: -------------------------------------------------------------------------------- 1 | package display 2 | 3 | import ( 4 | "github.com/aykevl/board" 5 | "github.com/aykevl/tinygl" 6 | "github.com/aykevl/tinygl/style" 7 | "github.com/aykevl/tinygl/style/basic" 8 | "github.com/hybridgroup/mechanoid/convert" 9 | "github.com/orsinium-labs/wypes" 10 | "tinygo.org/x/drivers/pixel" 11 | ) 12 | 13 | var pongCount int 14 | 15 | type Device[T pixel.Color] struct { 16 | Display board.Displayer[T] 17 | Screen *tinygl.Screen[T] 18 | Theme *basic.Basic[T] 19 | VBox *tinygl.VBox[T] 20 | Header *tinygl.Text[T] 21 | PingText *tinygl.Text[T] 22 | PongText *tinygl.Text[T] 23 | } 24 | 25 | // NewDevice creates a new display device. 26 | func NewDevice[T pixel.Color](display board.Displayer[T]) *Device[T] { 27 | return &Device[T]{ 28 | Display: display, 29 | } 30 | } 31 | 32 | func (d *Device[T]) Modules() wypes.Modules { 33 | return wypes.Modules{ //} 34 | "hosted": wypes.Module{ 35 | "pong": wypes.H0(d.pongFunc), 36 | }, 37 | } 38 | } 39 | 40 | func (d *Device[T]) Init() error { 41 | width, height := d.Display.Size() 42 | scalePercent := board.Display.PPI() * 100 / 120 43 | 44 | // Initialize the screen. 45 | buf := pixel.NewImage[T](int(width), int(height)/4) 46 | d.Screen = tinygl.NewScreen[T](d.Display, buf, board.Display.PPI()) 47 | d.Theme = basic.NewTheme(style.NewScale(scalePercent), d.Screen) 48 | d.Header = d.Theme.NewText("Hello, Mechanoid") 49 | d.PingText = d.Theme.NewText("waiting...") 50 | d.PongText = d.Theme.NewText("waiting...") 51 | d.VBox = d.Theme.NewVBox(d.Header, d.PingText, d.PongText) 52 | 53 | d.Screen.SetChild(d.VBox) 54 | d.Screen.Update() 55 | board.Display.SetBrightness(board.Display.MaxBrightness()) 56 | 57 | return nil 58 | } 59 | 60 | func (d *Device[T]) ShowPing(count int) { 61 | msg := "Ping: " + convert.IntToString(count) 62 | d.PingText.SetText(msg) 63 | d.Screen.Update() 64 | } 65 | 66 | func (d *Device[T]) ShowPong(count int) { 67 | msg := "Pong: " + convert.IntToString(count) 68 | d.PongText.SetText(msg) 69 | d.Screen.Update() 70 | } 71 | 72 | func (d *Device[T]) pongFunc() wypes.Void { 73 | pongCount++ 74 | println("Pong", pongCount) 75 | d.ShowPong(pongCount) 76 | return wypes.Void{} 77 | } 78 | -------------------------------------------------------------------------------- /display/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/display 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/aykevl/board v0.0.0-20240106144210-80ca76f77def 7 | github.com/aykevl/tinygl v0.0.0-20240131130748-3033a2fd9182 8 | github.com/hybridgroup/mechanoid v0.2.0 9 | github.com/orsinium-labs/wypes v0.1.4 10 | tinygo.org/x/drivers v0.26.1-0.20231124130000-fef6564044f9 11 | ) 12 | 13 | require ( 14 | fyne.io/fyne/v2 v2.3.4 // indirect 15 | fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/fredbi/uri v0.1.0 // indirect 18 | github.com/fsnotify/fsnotify v1.5.4 // indirect 19 | github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect 20 | github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect 21 | github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect 22 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect 23 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect 24 | github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 // indirect 25 | github.com/godbus/dbus/v5 v5.1.0 // indirect 26 | github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect 27 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 28 | github.com/gopherjs/gopherjs v1.17.2 // indirect 29 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 30 | github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect 31 | github.com/pmezard/go-difflib v1.0.0 // indirect 32 | github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 // indirect 33 | github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect 34 | github.com/stretchr/testify v1.8.0 // indirect 35 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 36 | github.com/tevino/abool v1.2.0 // indirect 37 | github.com/yuin/goldmark v1.4.13 // indirect 38 | golang.org/x/image v0.3.0 // indirect 39 | golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect 40 | golang.org/x/net v0.7.0 // indirect 41 | golang.org/x/sys v0.5.0 // indirect 42 | golang.org/x/text v0.7.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /display/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "time" 7 | 8 | "github.com/aykevl/board" 9 | "github.com/hybridgroup/mechanoid-examples/display/devices/display" 10 | "github.com/hybridgroup/mechanoid/engine" 11 | "github.com/hybridgroup/mechanoid/interp" 12 | "tinygo.org/x/drivers/pixel" 13 | ) 14 | 15 | //go:embed modules/ping.wasm 16 | var wasmCode []byte 17 | 18 | var ( 19 | eng *engine.Engine 20 | 21 | pingCount, pongCount int 22 | ) 23 | 24 | // main func just calls a run() func so we can infer the display type. 25 | func main() { 26 | run(board.Display.Configure()) 27 | } 28 | 29 | // run func is the main entry point for the program. 30 | func run[T pixel.Color](disp board.Displayer[T]) { 31 | time.Sleep(2 * time.Second) 32 | 33 | println("Mechanoid engine starting...") 34 | eng := engine.NewEngine() 35 | eng.UseInterpreter(interp.NewInterpreter()) 36 | eng.AddDevice(display.NewDevice(disp)) 37 | 38 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 39 | if err := eng.Init(); err != nil { 40 | println(err.Error()) 41 | return 42 | } 43 | 44 | println("Loading and running WASM code...") 45 | ins, err := eng.LoadAndRun(bytes.NewReader(wasmCode)) 46 | if err != nil { 47 | println(err.Error()) 48 | return 49 | } 50 | 51 | for { 52 | pingCount++ 53 | println("Ping", pingCount) 54 | 55 | if _, err := ins.Call("ping"); err != nil { 56 | println(err.Error()) 57 | } 58 | 59 | eng.Devices[0].(*display.Device[T]).ShowPing(pingCount) 60 | 61 | time.Sleep(1 * time.Second) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /display/modules/ping.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/display/modules/ping.wasm -------------------------------------------------------------------------------- /display/modules/ping/README.md: -------------------------------------------------------------------------------- 1 | # Ping 2 | 3 | Exports a `ping()` function, that immediately calls the host's `pong()` function. 4 | 5 | Mechanoid WebAssembly module template. 6 | 7 | ## Building 8 | -------------------------------------------------------------------------------- /display/modules/ping/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/display/modules/ping 2 | 3 | go 1.22.0 4 | -------------------------------------------------------------------------------- /display/modules/ping/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport hosted pong 6 | func pong() 7 | 8 | //go:export ping 9 | func ping() { 10 | pong() 11 | } 12 | 13 | func main() {} 14 | -------------------------------------------------------------------------------- /display/modules/ping/ping.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mno-bulk-memory", 15 | "-mnontrapping-fptoint", 16 | "-msign-ext" 17 | ], 18 | "ldflags": [ 19 | "--stack-first", 20 | "--no-demangle", 21 | "--no-entry", 22 | "--import-memory", 23 | "--initial-memory=65536", 24 | "--max-memory=65536", 25 | "-zstack-size=4096" 26 | ], 27 | "extra-files": [ 28 | "src/runtime/asm_tinygowasm.S" 29 | ], 30 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 31 | } 32 | -------------------------------------------------------------------------------- /externref/README.md: -------------------------------------------------------------------------------- 1 | # Externalref 2 | 3 | Here is an example of an application built using Mechanoid that uses host external references. 4 | 5 | For more information, see https://webassembly.github.io/gc/core/syntax/types.html#reference-types 6 | 7 | ## How it works 8 | 9 | The application creates a new module. 10 | 11 | It then loads the `hollaback.wasm` program which is embedded into the application itself. 12 | 13 | That module is able to obtain an externref to the host instance, and then use it to call the associated host method. 14 | 15 | ## How to run 16 | 17 | ### PyBadge 18 | 19 | ``` 20 | $ mecha flash -i wazero -m pybadge 21 | Building module hollaback 22 | Done. 23 | code data bss | flash ram 24 | 112 33 65503 | 145 65536 25 | Application built. Now flashing... 26 | code data bss | flash ram 27 | 331152 66348 7112 | 397500 73460 28 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 29 | Mechanoid engine starting... 30 | Using interpreter wazero 31 | Initializing engine... 32 | Initializing interpreter... 33 | Initializing devices... 34 | Defining host function... 35 | Loading WASM module... 36 | Running module... 37 | Calling start... 38 | newGreeter msg is Hello, WebAssembly! 39 | got value: 1 40 | Calling update... 41 | got value: 1 42 | hello msg is From Mechanoid 43 | Calling update... 44 | got value: 1 45 | hello msg is From Mechanoid 46 | Calling update... 47 | got value: 1 48 | ... 49 | ``` 50 | -------------------------------------------------------------------------------- /externref/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/externref 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/hybridgroup/mechanoid v0.2.0 7 | github.com/orsinium-labs/wypes v0.1.4 8 | ) 9 | 10 | require ( 11 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 12 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /externref/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.2.0 h1:2Kg3c0cK9JK8ZeJkiN+l2o4B0QJYnNHkNEKraMFOugQ= 2 | github.com/hybridgroup/mechanoid v0.2.0/go.mod h1:7jQEJ0blXXbIcqmu2/d7vSxWL5Dxbt18zZirJ35OeXc= 3 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 h1:pP916/7OLromi+7fHcbw5jIXOT/idiEBQ3ucbl8caz8= 4 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834/go.mod h1:rLavUo4P0xVcDeDnViYEpPQPoACmp1py9UTLPY/R7Lg= 5 | github.com/orsinium-labs/tinytest v1.0.0 h1:YiGm/whlGm3mn/ynx9CCFuvEa3Q6yEGrzrKXEqJOkdc= 6 | github.com/orsinium-labs/tinytest v1.0.0/go.mod h1:GwcYBp0aKi6zujzBXFpCnqw6RSLSp9JSedDyu/V1DF4= 7 | github.com/orsinium-labs/wypes v0.1.4 h1:+7oih2IDlEpz7laiL3sQFlIU8vjd5j/SwcYlPdIPc0Q= 8 | github.com/orsinium-labs/wypes v0.1.4/go.mod h1:FSNWGo8I6/D5RYXMkCxyu71TXJAlwJGQUxgs4i6MAwo= 9 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 h1:sB7EW6YA2z8k4or+w8Tm2eo1wD/f4uQ31AffFfgRhvo= 10 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 11 | -------------------------------------------------------------------------------- /externref/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "time" 7 | 8 | "github.com/hybridgroup/mechanoid/engine" 9 | "github.com/hybridgroup/mechanoid/interp" 10 | "github.com/orsinium-labs/wypes" 11 | ) 12 | 13 | //go:embed modules/hollaback.wasm 14 | var wasmCode []byte 15 | 16 | func main() { 17 | time.Sleep(3 * time.Second) 18 | 19 | println("Mechanoid engine starting...") 20 | eng := engine.NewEngine() 21 | eng.UseInterpreter(interp.NewInterpreter()) 22 | 23 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 24 | if err := eng.Init(); err != nil { 25 | println(err.Error()) 26 | return 27 | } 28 | 29 | println("Defining host function...") 30 | modules := wypes.Modules{ 31 | "greeter": wypes.Module{ 32 | "new": wypes.H1(newGreeter), 33 | "hello": wypes.H2(hello), 34 | "print_u32": wypes.H1(printU32), 35 | }, 36 | } 37 | if err := eng.Interpreter.SetModules(modules); err != nil { 38 | println(err.Error()) 39 | return 40 | } 41 | 42 | println("Loading and running WASM code...") 43 | ins, err := eng.LoadAndRun(bytes.NewReader(wasmCode)) 44 | if err != nil { 45 | println(err.Error()) 46 | return 47 | } 48 | 49 | println("Calling start...") 50 | if _, err := ins.Call("start"); err != nil { 51 | println(err.Error()) 52 | } 53 | for { 54 | println("Calling update...") 55 | if _, err := ins.Call("update"); err != nil { 56 | println(err.Error()) 57 | } 58 | 59 | time.Sleep(1 * time.Second) 60 | } 61 | } 62 | 63 | type greeter struct { 64 | greeting string 65 | } 66 | 67 | func newGreeter(msg wypes.String) wypes.HostRef[greeter] { 68 | println("newGreeter msg is", msg.Unwrap()) 69 | // create the badge UI element 70 | g := greeter{ 71 | greeting: msg.Unwrap(), 72 | } 73 | return wypes.HostRef[greeter]{Raw: g} 74 | } 75 | 76 | func hello(ref wypes.HostRef[greeter], msg wypes.String) wypes.Void { 77 | println("hello msg is", msg.Unwrap()) 78 | g := ref.Unwrap() 79 | g.greeting = msg.Unwrap() 80 | return wypes.Void{} 81 | } 82 | 83 | func printU32(x wypes.UInt32) wypes.Void { 84 | println("got value:", x.Unwrap()) 85 | return wypes.Void{} 86 | } 87 | -------------------------------------------------------------------------------- /externref/modules/hollaback.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/externref/modules/hollaback.wasm -------------------------------------------------------------------------------- /externref/modules/hollaback/README.md: -------------------------------------------------------------------------------- 1 | # Hello 2 | 3 | Mechanoid WebAssembly module template. 4 | 5 | ## Building 6 | 7 | -------------------------------------------------------------------------------- /externref/modules/hollaback/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/externref/modules/hollaback 2 | 3 | go 1.22.0 4 | 5 | require github.com/hybridgroup/mechanoid v0.0.0-20240308111911-97652132727d 6 | -------------------------------------------------------------------------------- /externref/modules/hollaback/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.0.0-20240308111911-97652132727d h1:KRCQ1daf3LiWzL0nIxzWNdHmDFLnwLFGhCmuEmyR1cA= 2 | github.com/hybridgroup/mechanoid v0.0.0-20240308111911-97652132727d/go.mod h1:goPLq6Qim+Hr7nGeDflbxPNqhErDAdhpUmFp44URbo8= 3 | -------------------------------------------------------------------------------- /externref/modules/hollaback/hollaback.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mno-bulk-memory", 15 | "-mnontrapping-fptoint", 16 | "-msign-ext" 17 | ], 18 | "ldflags": [ 19 | "--stack-first", 20 | "--no-demangle", 21 | "--no-entry", 22 | "--initial-memory=65536", 23 | "--max-memory=65536", 24 | "-zstack-size=4096" 25 | ], 26 | "extra-files": [ 27 | "src/runtime/asm_tinygowasm.S" 28 | ], 29 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 30 | } 31 | -------------------------------------------------------------------------------- /externref/modules/hollaback/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/hybridgroup/mechanoid/convert" 7 | ) 8 | 9 | //go:wasmimport greeter new 10 | func new_greeter(ptr, sz uint32) uint32 11 | 12 | //go:wasmimport greeter hello 13 | func greeter_hello(ref, ptr, sz uint32) 14 | 15 | //go:wasmimport greeter print_u32 16 | func print_u32(x uint32) 17 | 18 | const ( 19 | msg = "Hello, WebAssembly!" 20 | msg2 = "From Mechanoid" 21 | ) 22 | 23 | var ( 24 | // ref is an externref that is returned from the new_greeter host function. 25 | ref uint32 26 | 27 | // buf is a buffer that is used to pass messages to the greeter host instance. 28 | buf [64]byte 29 | ) 30 | 31 | //go:export start 32 | func start() { 33 | copy(buf[:], msg) 34 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 35 | 36 | // ref is an externref that is passed to the hello function. 37 | // this is an opaque reference to the greeter instance on the host. 38 | // The host is responsible for managing the lifetime of the instance. 39 | // It is not a pointer to the instance, but a reference to it. 40 | ref = new_greeter(ptr, sz) 41 | print_u32(ref) 42 | } 43 | 44 | //go:export update 45 | func update() { 46 | print_u32(ref) 47 | copy(buf[:], msg2) 48 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg2)]) 49 | greeter_hello(ref, ptr, sz) 50 | } 51 | 52 | func main() {} 53 | -------------------------------------------------------------------------------- /filestore/README.md: -------------------------------------------------------------------------------- 1 | # Filestore 2 | 3 | Application that demonstrates how to use the onboard Flash storage on the hardware device to save/load/run external WASM modules via a Command line interface directly on the device itself. 4 | 5 | ## How to run 6 | 7 | ### Flash the board 8 | 9 | ```bash 10 | $ mecha flash -m pybadge 11 | Building TinyGo module ping 12 | Done. 13 | code data bss | flash ram 14 | 9 0 0 | 9 0 15 | Building Rust module pingrs 16 | Done. 17 | warning: unstable feature specified for `-Ctarget-feature`: `atomics` 18 | | 19 | = note: this feature is not stably supported; its behavior can change in the future 20 | warning: unstable feature specified for `-Ctarget-feature`: `bulk-memory` 21 | | 22 | = note: this feature is not stably supported; its behavior can change in the future 23 | warning: `pingrs` (lib) generated 2 warnings 24 | Finished release [optimized] target(s) in 0.00s 25 | Building Zig module pingzig 26 | Done. 27 | Application built. Now flashing... 28 | code data bss | flash ram 29 | 342556 16812 7224 | 359368 2403 30 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 31 | 32 | ==> 33 | ``` 34 | 35 | You should see the `==>` prompt. See "How to use" below. 36 | 37 | ## How to use 38 | 39 | You should see a `==>` prompt. Try the `lsblk` command to see the Flash storage information: 40 | 41 | ```bash 42 | ==> lsblk 43 | ------------------------------------- 44 | Device Information: 45 | ------------------------------------- 46 | flash data start: 0x00024000 47 | flash data end: 0x00080000 48 | ------------------------------------- 49 | ``` 50 | 51 | This the the available Flash memory on your board in the extra space not being used by your program. 52 | 53 | Try the `ls` command. 54 | 55 | ```bash 56 | ==> ls 57 | 58 | ------------------------------------- 59 | File Store: 60 | ------------------------------------- 61 | 62 | ------------------------------------- 63 | ``` 64 | 65 | You do not yet have any WASM files in the Flash storage. Let's put one on the device using the `save` command. 66 | 67 | The easiest way to do this is the included `savefile` program. Press `CTRL-C` to return to your shell, then run the following command (substitute the correct port name for `/dev/ttyACM0` as needed): 68 | 69 | ```bash 70 | cd ./filestore 71 | 72 | go run ./savefile ./modules/ping.wasm /dev/ttyACM0 73 | ``` 74 | 75 | Now connect again to the board, and now you should see the file listed using the `ls` command: 76 | 77 | ```bash 78 | $ mecha monitor 79 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 80 | 81 | ==> ls 82 | 83 | ------------------------------------- 84 | File Store: 85 | ------------------------------------- 86 | 370 ping.wasm 87 | 88 | ------------------------------------- 89 | ``` 90 | 91 | You can now load the module: 92 | 93 | ```bash 94 | ==> load ping.wasm 95 | loading ping.wasm 96 | module loaded. 97 | ``` 98 | 99 | And then start it running: 100 | 101 | ```bash 102 | ==> run 103 | module running. 104 | ``` 105 | 106 | Use the `ping` command: 107 | 108 | ```bash 109 | ==> ping 3 110 | Ping... 111 | pong 112 | Ping... 113 | pong 114 | Ping... 115 | pong 116 | ``` 117 | 118 | Use the `halt` command to stop the module. 119 | 120 | ```bash 121 | ==> halt 122 | halting... 123 | module halted. 124 | ``` 125 | 126 | You can load another module now. Let's try one written using Rust. 127 | 128 | First, transfer the compiled pingrs.wasm module to the board. Press `CTRL-C` to return to your shell, then run the following command: 129 | 130 | ```bash 131 | go run ./savefile ./modules/pingrs.wasm /dev/ttyACM0 132 | ``` 133 | 134 | Now connect again to the board, and now you should see the file listed using the `ls` command, alongside the previously saved file: 135 | 136 | ```bash 137 | $ mecha monitor 138 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 139 | 140 | ==> ls 141 | 142 | ------------------------------------- 143 | File Store: 144 | ------------------------------------- 145 | 370 ping.wasm 146 | 324 pingrs.wasm 147 | ------------------------------------- 148 | ``` 149 | 150 | You can now load the Rust module: 151 | 152 | ```bash 153 | ==> load pingrs.wasm 154 | loading pingrs.wasm 155 | module loaded. 156 | ``` 157 | 158 | start it running: 159 | 160 | ```bash 161 | ==> run 162 | module running. 163 | ``` 164 | 165 | And use the `ping` command to call the Rust module: 166 | 167 | ```bash 168 | ==> ping 3 169 | Ping... 170 | pong 171 | Ping... 172 | pong 173 | Ping... 174 | pong 175 | ``` 176 | -------------------------------------------------------------------------------- /filestore/baremetal.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | 8 | "github.com/hybridgroup/mechanoid/filestore/flash" 9 | ) 10 | 11 | var ( 12 | console = machine.Serial 13 | fs = &flash.FileStore{} 14 | ) 15 | 16 | func dataStart() uint32 { 17 | return uint32(machine.FlashDataStart()) 18 | } 19 | 20 | func dataEnd() uint32 { 21 | return uint32(machine.FlashDataEnd()) 22 | } 23 | -------------------------------------------------------------------------------- /filestore/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "runtime" 6 | "time" 7 | 8 | "encoding/binary" 9 | "encoding/hex" 10 | "strings" 11 | 12 | "github.com/hybridgroup/mechanoid/convert" 13 | "github.com/hybridgroup/mechanoid/engine" 14 | ) 15 | 16 | const consoleBufLen = 64 17 | 18 | const ( 19 | StateInput = iota 20 | StateEscape 21 | StateEscBrc 22 | StateCSI 23 | ) 24 | 25 | var ( 26 | input [consoleBufLen]byte 27 | state = StateInput 28 | 29 | commands = map[string]cmdfunc{ 30 | "": noop, 31 | "ls": ls, 32 | "lsblk": lsblk, 33 | "load": load, 34 | "save": save, 35 | "rm": rm, 36 | "run": run, 37 | "halt": halt, 38 | "ping": ping, 39 | } 40 | ) 41 | 42 | func cli() { 43 | prompt() 44 | 45 | for i := 0; ; { 46 | if console.Buffered() > 0 { 47 | data, _ := console.ReadByte() 48 | switch state { 49 | case StateInput: 50 | switch data { 51 | case 0x8: 52 | fallthrough 53 | case 0x7f: // this is probably wrong... works on my machine tho :) 54 | // backspace 55 | if i > 0 { 56 | i -= 1 57 | console.Write([]byte{0x8, 0x20, 0x8}) 58 | } 59 | case 13: 60 | // return key 61 | if console.Buffered() > 0 { 62 | data, _ := console.ReadByte() 63 | if data != 10 { 64 | println("\r\nunexpected: \r", int(data)) 65 | } 66 | } 67 | console.Write([]byte("\r\n")) 68 | runCommand(string(input[:i])) 69 | prompt() 70 | 71 | i = 0 72 | continue 73 | case 27: 74 | // escape 75 | state = StateEscape 76 | default: 77 | // anything else, just echo the character if it is printable 78 | if i < (consoleBufLen - 1) { 79 | console.WriteByte(data) 80 | input[i] = data 81 | i++ 82 | } 83 | } 84 | case StateEscape: 85 | switch data { 86 | case 0x5b: 87 | state = StateEscBrc 88 | default: 89 | state = StateInput 90 | } 91 | default: 92 | // TODO: handle escape sequences 93 | state = StateInput 94 | } 95 | } 96 | time.Sleep(10 * time.Millisecond) 97 | } 98 | } 99 | 100 | func runCommand(line string) { 101 | defer func() { 102 | p := recover() 103 | if p != nil { 104 | println("panic:", p) 105 | } 106 | }() 107 | 108 | argv := strings.SplitN(strings.TrimSpace(line), " ", -1) 109 | cmd := argv[0] 110 | cmdfn, ok := commands[cmd] 111 | if !ok { 112 | println("unknown command: " + line) 113 | return 114 | } 115 | cmdfn(argv) 116 | } 117 | 118 | func prompt() { 119 | print("==> ") 120 | } 121 | 122 | type cmdfunc func(argv []string) 123 | 124 | func noop(argv []string) {} 125 | 126 | func ls(argv []string) { 127 | if eng.FileStore == nil { 128 | println("no file store available") 129 | return 130 | } 131 | 132 | list, err := eng.FileStore.List() 133 | if err != nil { 134 | println("error listing files:", err.Error()) 135 | return 136 | } 137 | 138 | print( 139 | "\n-------------------------------------\r\n" + 140 | " File Store: \r\n" + 141 | "-------------------------------------\r\n") 142 | for _, file := range list { 143 | println(file.Size(), file.Name()) 144 | } 145 | print( 146 | "\n-------------------------------------\r\n\r\n") 147 | } 148 | 149 | func lsblk(argv []string) { 150 | b := make([]byte, 4) 151 | binary.BigEndian.PutUint32(b, uint32(dataStart())) 152 | start := hex.EncodeToString(b) 153 | 154 | binary.BigEndian.PutUint32(b, uint32(dataEnd())) 155 | end := hex.EncodeToString(b) 156 | 157 | print( 158 | "\n-------------------------------------\r\n" + 159 | " Device Information: \r\n" + 160 | "-------------------------------------\r\n" + 161 | " flash data start: 0x" + start + "\r\n" + 162 | " flash data end: 0x" + end + "\r\n" + 163 | "-------------------------------------\r\n\r\n") 164 | } 165 | 166 | // load module into engine 167 | func load(argv []string) { 168 | if eng.FileStore == nil { 169 | println("no file store available") 170 | return 171 | } 172 | 173 | if len(argv) != 2 { 174 | println("usage: save ") 175 | return 176 | } 177 | 178 | if running { 179 | println("already running. halt first.") 180 | return 181 | } 182 | 183 | println("loading", argv[1]) 184 | 185 | n, err := eng.FileStore.FileSize(argv[1]) 186 | if err != nil { 187 | println("error loading file:", err.Error()) 188 | return 189 | } 190 | 191 | data := make([]byte, n) 192 | if err := eng.FileStore.Load(argv[1], data); err != nil { 193 | println("error loading file:", err.Error()) 194 | return 195 | } 196 | 197 | if err := eng.Interpreter.Load(bytes.NewReader(data)); err != nil { 198 | println(err.Error()) 199 | return 200 | } 201 | println("module loaded.") 202 | } 203 | 204 | // save into filestore 205 | func save(argv []string) { 206 | if eng.FileStore == nil { 207 | println("no file store available") 208 | return 209 | } 210 | 211 | if len(argv) != 3 { 212 | println("usage: save ") 213 | return 214 | } 215 | 216 | // read in size bytes from port 217 | sz := convert.StringToInt(argv[2]) 218 | 219 | data := make([]byte, sz) 220 | if err := readDataFromPort(data); err != nil { 221 | println("error reading data:", err.Error()) 222 | return 223 | } 224 | 225 | if err := eng.FileStore.Save(argv[1], data); err != nil { 226 | println("error saving file:", err.Error()) 227 | } 228 | } 229 | 230 | func readDataFromPort(data []byte) (err error) { 231 | for i := 0; i < len(data); i++ { 232 | for console.Buffered() == 0 { 233 | } 234 | data[i], err = console.ReadByte() 235 | if err != nil { 236 | return 237 | } 238 | } 239 | return nil 240 | } 241 | 242 | // remove from filestore 243 | func rm(argv []string) { 244 | if eng.FileStore == nil { 245 | println("no file store available") 246 | return 247 | } 248 | 249 | if len(argv) != 2 { 250 | println("usage: rm ") 251 | return 252 | } 253 | 254 | if err := eng.FileStore.Remove(argv[1]); err != nil { 255 | println("error removing file:", err.Error()) 256 | return 257 | } 258 | 259 | println(argv[1], "deleted.") 260 | } 261 | 262 | var ( 263 | instance engine.Instance 264 | running bool 265 | ) 266 | 267 | func run(argv []string) { 268 | if eng.FileStore == nil { 269 | println("no file store available") 270 | return 271 | } 272 | 273 | if running { 274 | println("module already running. run 'halt' first.") 275 | return 276 | } 277 | 278 | // run the module 279 | var err error 280 | instance, err = eng.Interpreter.Run() 281 | if err != nil { 282 | println(err.Error()) 283 | return 284 | } 285 | 286 | running = true 287 | println("module running.") 288 | } 289 | 290 | func halt(argv []string) { 291 | if !running { 292 | println("module not running") 293 | return 294 | } 295 | 296 | println("halting...") 297 | _ = eng.Interpreter.Halt() 298 | instance = nil 299 | running = false 300 | runtime.GC() 301 | println("module halted.") 302 | } 303 | 304 | func ping(argv []string) { 305 | if eng.FileStore == nil { 306 | println("no file store available") 307 | return 308 | } 309 | 310 | if !running { 311 | println("module not running. use 'run' first.") 312 | return 313 | } 314 | 315 | if len(argv) < 2 { 316 | println("usage: ping ") 317 | return 318 | } 319 | count := convert.StringToInt(argv[1]) 320 | 321 | for i := 0; i < count; i++ { 322 | println("Ping...") 323 | if _, err := instance.Call("ping"); err != nil { 324 | println(err.Error()) 325 | return 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /filestore/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/filestore 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/hybridgroup/mechanoid v0.2.0 7 | github.com/orsinium-labs/wypes v0.1.4 8 | go.bug.st/serial v1.6.2 9 | ) 10 | 11 | require ( 12 | github.com/creack/goselect v0.1.2 // indirect 13 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 14 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 15 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect 16 | tinygo.org/x/tinyfs v0.3.1-0.20231212053859-32ae3f6bbad9 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /filestore/go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= 2 | github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/hybridgroup/mechanoid v0.2.0 h1:2Kg3c0cK9JK8ZeJkiN+l2o4B0QJYnNHkNEKraMFOugQ= 6 | github.com/hybridgroup/mechanoid v0.2.0/go.mod h1:7jQEJ0blXXbIcqmu2/d7vSxWL5Dxbt18zZirJ35OeXc= 7 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 h1:pP916/7OLromi+7fHcbw5jIXOT/idiEBQ3ucbl8caz8= 8 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834/go.mod h1:rLavUo4P0xVcDeDnViYEpPQPoACmp1py9UTLPY/R7Lg= 9 | github.com/orsinium-labs/tinytest v1.0.0 h1:YiGm/whlGm3mn/ynx9CCFuvEa3Q6yEGrzrKXEqJOkdc= 10 | github.com/orsinium-labs/tinytest v1.0.0/go.mod h1:GwcYBp0aKi6zujzBXFpCnqw6RSLSp9JSedDyu/V1DF4= 11 | github.com/orsinium-labs/wypes v0.1.4 h1:+7oih2IDlEpz7laiL3sQFlIU8vjd5j/SwcYlPdIPc0Q= 12 | github.com/orsinium-labs/wypes v0.1.4/go.mod h1:FSNWGo8I6/D5RYXMkCxyu71TXJAlwJGQUxgs4i6MAwo= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 16 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 17 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 h1:sB7EW6YA2z8k4or+w8Tm2eo1wD/f4uQ31AffFfgRhvo= 18 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 19 | go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8= 20 | go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= 21 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= 22 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 24 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 25 | tinygo.org/x/tinyfs v0.3.1-0.20231212053859-32ae3f6bbad9 h1:u3w9x/Jo4A5Q+WxABVd15dD2ScTI7B6DmGb3lTKNP/w= 26 | tinygo.org/x/tinyfs v0.3.1-0.20231212053859-32ae3f6bbad9/go.mod h1:smxJYYJRbfvbQofdYoECIBPHQZJTKd4YeS3ZLvx+lpQ= 27 | -------------------------------------------------------------------------------- /filestore/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "time" 6 | 7 | "github.com/hybridgroup/mechanoid/engine" 8 | "github.com/hybridgroup/mechanoid/interp" 9 | "github.com/orsinium-labs/wypes" 10 | ) 11 | 12 | var ( 13 | eng *engine.Engine 14 | ) 15 | 16 | func main() { 17 | time.Sleep(1 * time.Second) 18 | 19 | println("Mechanoid engine starting...") 20 | eng = engine.NewEngine() 21 | eng.UseInterpreter(interp.NewInterpreter()) 22 | eng.UseFileStore(fs) 23 | 24 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 25 | if err := eng.Init(); err != nil { 26 | println(err.Error()) 27 | return 28 | } 29 | 30 | modules := wypes.Modules{ 31 | "hosted": wypes.Module{ 32 | "pong": wypes.H0(pongFunc), 33 | }, 34 | } 35 | if err := eng.Interpreter.SetModules(modules); err != nil { 36 | println(err.Error()) 37 | return 38 | } 39 | // start up CLI 40 | cli() 41 | } 42 | 43 | func pongFunc() wypes.Void { 44 | println("pong") 45 | return wypes.Void{} 46 | } 47 | -------------------------------------------------------------------------------- /filestore/modules/ping.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/filestore/modules/ping.wasm -------------------------------------------------------------------------------- /filestore/modules/ping/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) The Hybrid Group 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | --- LLVM Exceptions to the Apache 2.0 License ---- 205 | 206 | As an exception, if, as a result of your compiling your source code, portions 207 | of this Software are embedded into an Object form of such source code, you 208 | may redistribute such embedded portions in such Object form without complying 209 | with the conditions of Sections 4(a), 4(b) and 4(d) of the License. 210 | 211 | In addition, if you combine or link compiled forms of this Software with 212 | software that is licensed under the GPLv2 ("Combined Software") and if a 213 | court of competent jurisdiction determines that the patent provision (Section 214 | 3), the indemnity provision (Section 9) or other Section of the License 215 | conflicts with the conditions of the GPLv2, you may retroactively and 216 | prospectively choose to deem waived or otherwise exclude such Section(s) of 217 | the License, but only in their entirety and only with respect to the Combined 218 | Software. 219 | -------------------------------------------------------------------------------- /filestore/modules/ping/README.md: -------------------------------------------------------------------------------- 1 | # Ping 2 | 3 | Exports a `ping()` function, that immediately calls the host's `pong()` function. 4 | 5 | Mechanoid WebAssembly module template. 6 | 7 | ## Building 8 | 9 | ``` 10 | mecha build 11 | ``` -------------------------------------------------------------------------------- /filestore/modules/ping/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/filestore/modules/ping 2 | 3 | go 1.22.0 4 | 5 | replace github.com/tetratelabs/wazero => github.com/orsinium-forks/wazero v0.0.0-20240305131633-28fdf656fe85 6 | -------------------------------------------------------------------------------- /filestore/modules/ping/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport hosted pong 6 | func pong() 7 | 8 | //go:export ping 9 | func ping() { 10 | pong() 11 | } 12 | 13 | func main() {} 14 | -------------------------------------------------------------------------------- /filestore/modules/ping/ping.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mno-bulk-memory", 15 | "-mnontrapping-fptoint", 16 | "-msign-ext" 17 | ], 18 | "ldflags": [ 19 | "--stack-first", 20 | "--no-demangle", 21 | "--no-entry", 22 | "--import-memory", 23 | "--initial-memory=65536", 24 | "--max-memory=65536", 25 | "-zstack-size=4096" 26 | ], 27 | "extra-files": [ 28 | "src/runtime/asm_tinygowasm.S" 29 | ], 30 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 31 | } 32 | -------------------------------------------------------------------------------- /filestore/modules/pingrs.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/filestore/modules/pingrs.wasm -------------------------------------------------------------------------------- /filestore/modules/pingrs/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target."wasm32-unknown-unknown"] 2 | rustflags = ["-C", "target-feature=+atomics,+bulk-memory", 3 | "-C", "link-arg=--initial-memory=65536", 4 | "-C", "link-arg=--max-memory=65536", 5 | "-C", "link-arg=-zstack-size=4096"] 6 | -------------------------------------------------------------------------------- /filestore/modules/pingrs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /filestore/modules/pingrs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "pingrs" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /filestore/modules/pingrs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pingrs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | [lib] 11 | crate-type = ["cdylib"] 12 | -------------------------------------------------------------------------------- /filestore/modules/pingrs/README.md: -------------------------------------------------------------------------------- 1 | # pingrs 2 | 3 | WASM unknown module written in Rust 4 | 5 | ## How to install 6 | 7 | - Install Rust. 8 | 9 | - Install the `wasm32-unknown-unknown` target. 10 | 11 | ```bash 12 | rustup target add wasm32-unknown-unknown 13 | ``` 14 | 15 | ## How to build module 16 | 17 | ```bash 18 | cd modules/pingrs 19 | cargo build --target wasm32-unknown-unknown --release 20 | cd ../.. 21 | cp ./modules/pingrs/target/wasm32-unknown-unknown/release/pingrs.wasm ./modules/ 22 | ``` 23 | -------------------------------------------------------------------------------- /filestore/modules/pingrs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[panic_handler] 4 | fn handle_panic(_: &core::panic::PanicInfo) -> ! { 5 | core::arch::wasm32::unreachable() 6 | } 7 | 8 | #[no_mangle] 9 | pub extern fn ping() { 10 | unsafe {pong()}; 11 | } 12 | 13 | #[link(wasm_import_module = "hosted")] 14 | extern { 15 | fn pong(); 16 | } 17 | -------------------------------------------------------------------------------- /filestore/modules/pingzig.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/filestore/modules/pingzig.wasm -------------------------------------------------------------------------------- /filestore/modules/pingzig/.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out/ 3 | -------------------------------------------------------------------------------- /filestore/modules/pingzig/README.md: -------------------------------------------------------------------------------- 1 | # PingZig 2 | 3 | Mechanoid module template in Zig that exports a `ping()` function, that immediately calls the host's `pong()` function. 4 | 5 | ## Building 6 | 7 | ``` 8 | mecha build 9 | ``` 10 | -------------------------------------------------------------------------------- /filestore/modules/pingzig/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | const lib = b.addSharedLibrary(.{ 5 | .name = "pingzig", 6 | .root_source_file = .{ .path = "src/ping.zig" }, 7 | .target = .{ 8 | .cpu_arch = .wasm32, 9 | .os_tag = .freestanding, 10 | }, 11 | .optimize = .ReleaseSmall, 12 | .link_libc = true, 13 | }); 14 | lib.rdynamic = true; 15 | lib.stack_size = 4096; 16 | lib.initial_memory = 65536; 17 | lib.max_memory = 65536; 18 | 19 | b.installArtifact(lib); 20 | } 21 | -------------------------------------------------------------------------------- /filestore/modules/pingzig/src/ping.zig: -------------------------------------------------------------------------------- 1 | extern "hosted" fn pong() void; 2 | 3 | pub export fn ping() void { 4 | pong(); 5 | } 6 | -------------------------------------------------------------------------------- /filestore/os.go: -------------------------------------------------------------------------------- 1 | //go:build !tinygo 2 | 3 | package main 4 | 5 | import "github.com/hybridgroup/mechanoid/filestore/filesystem" 6 | 7 | var ( 8 | console = UART{} 9 | fs = &filesystem.FileStore{} 10 | ) 11 | 12 | type UARTConfig struct{} 13 | 14 | type UART struct { 15 | } 16 | 17 | // Configure the UART. 18 | func (uart *UART) Configure(config UARTConfig) { 19 | } 20 | 21 | // Read from the UART. 22 | func (uart *UART) Read(data []byte) (n int, err error) { 23 | return 0, nil 24 | } 25 | 26 | // Write to the UART. 27 | func (uart *UART) Write(data []byte) (n int, err error) { 28 | return 0, nil 29 | } 30 | 31 | // Buffered returns the number of bytes currently stored in the RX buffer. 32 | func (uart *UART) Buffered() int { 33 | return 0 34 | } 35 | 36 | // ReadByte reads a single byte from the UART. 37 | func (uart *UART) ReadByte() (byte, error) { 38 | return 0, nil 39 | } 40 | 41 | // WriteByte writes a single byte to the UART. 42 | func (uart *UART) WriteByte(b byte) error { 43 | return nil 44 | } 45 | 46 | func dataStart() uint32 { 47 | return 0 48 | } 49 | 50 | func dataEnd() uint32 { 51 | return 0 52 | } 53 | -------------------------------------------------------------------------------- /filestore/savefile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "time" 9 | 10 | "go.bug.st/serial" 11 | ) 12 | 13 | func main() { 14 | if len(os.Args) < 2 { 15 | fmt.Printf("usage: %s path/to/xxx.wasm port", os.Args[0]) 16 | os.Exit(0) 17 | } 18 | err := run(os.Args[1], os.Args[2]) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | } 23 | 24 | func run(target, port string) error { 25 | p, err := serial.Open(port, &serial.Mode{}) 26 | if err != nil { 27 | return err 28 | } 29 | defer p.Close() 30 | p.SetReadTimeout(1 * time.Second) 31 | 32 | b, err := os.ReadFile(target) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | fmt.Fprintf(p, "\r\nsave %s %d\r\n", filepath.Base(target), len(b)) 38 | 39 | buf := make([]byte, 1024) 40 | for { 41 | n, err := p.Read(buf) 42 | if err != nil || n == 0 { 43 | break 44 | } 45 | } 46 | 47 | _, err = p.Write(b) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /images/blinky-pybadge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/images/blinky-pybadge.jpg -------------------------------------------------------------------------------- /images/buttons-gopher-badge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/images/buttons-gopher-badge.jpg -------------------------------------------------------------------------------- /images/buttons-pybadge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/images/buttons-pybadge.jpg -------------------------------------------------------------------------------- /images/display-pybadge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/images/display-pybadge.jpg -------------------------------------------------------------------------------- /images/thumby.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/images/thumby.jpg -------------------------------------------------------------------------------- /images/wasmbadge-pybadge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/images/wasmbadge-pybadge.jpg -------------------------------------------------------------------------------- /images/wasmdrone-pybadge-tello.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/images/wasmdrone-pybadge-tello.jpg -------------------------------------------------------------------------------- /simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple 2 | 3 | Here is an example of a simple "ping" application built using Mechanoid. 4 | 5 | It consists of a host application that runs on a microcontroller, and a separate WebAssembly module that will be run by the host application on that same microcontroller. 6 | 7 | The host application loads the WASM and then executes it, sending the output to the serial interface on the board. This way we can see the output on your computer. 8 | 9 | ## How it works 10 | 11 | ```mermaid 12 | flowchart LR 13 | subgraph Computer 14 | end 15 | subgraph Microcontroller 16 | subgraph Application 17 | Pong 18 | end 19 | subgraph ping.wasm 20 | Ping 21 | end 22 | Ping-->Pong 23 | Application-->Ping 24 | end 25 | Application--Serial port-->Computer 26 | ``` 27 | 28 | ## How to run it 29 | 30 | ### Build the WASM module 31 | 32 | ``` 33 | $ mecha build 34 | Building module ping 35 | code data bss | flash ram 36 | 9 0 0 | 9 0 37 | ``` 38 | 39 | 40 | ### Flash the board 41 | 42 | PyBadge: 43 | 44 | ``` 45 | $ mecha flash -m pybadge 46 | code data bss | flash ram 47 | 101572 2044 6680 | 103616 8724 48 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 49 | Mechanoid engine starting... 50 | Using interpreter wasman 51 | Initializing engine... 52 | Defining host function... 53 | Loading WASM module... 54 | Running module... 55 | Calling ping... 56 | pong 57 | Calling ping... 58 | pong 59 | Calling ping... 60 | pong 61 | ``` 62 | 63 | Gopher Badge: 64 | 65 | ``` 66 | $ mecha flash -m gopher-badge 67 | code data bss | flash ram 68 | 109020 2044 3168 | 111064 5212 69 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 70 | Mechanoid engine starting... 71 | Using interpreter wasman 72 | Initializing engine... 73 | Defining host function... 74 | Loading WASM module... 75 | Running module... 76 | Calling ping... 77 | pong 78 | Calling ping... 79 | pong 80 | Calling ping... 81 | pong 82 | ``` 83 | 84 | You should see output start in your terminal from the microcontroller. 85 | -------------------------------------------------------------------------------- /simple/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/simple 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/hybridgroup/mechanoid v0.2.0 7 | github.com/orsinium-labs/wypes v0.1.4 8 | ) 9 | 10 | require ( 11 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 12 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /simple/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.2.0 h1:2Kg3c0cK9JK8ZeJkiN+l2o4B0QJYnNHkNEKraMFOugQ= 2 | github.com/hybridgroup/mechanoid v0.2.0/go.mod h1:7jQEJ0blXXbIcqmu2/d7vSxWL5Dxbt18zZirJ35OeXc= 3 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 h1:pP916/7OLromi+7fHcbw5jIXOT/idiEBQ3ucbl8caz8= 4 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834/go.mod h1:rLavUo4P0xVcDeDnViYEpPQPoACmp1py9UTLPY/R7Lg= 5 | github.com/orsinium-labs/tinytest v1.0.0 h1:YiGm/whlGm3mn/ynx9CCFuvEa3Q6yEGrzrKXEqJOkdc= 6 | github.com/orsinium-labs/tinytest v1.0.0/go.mod h1:GwcYBp0aKi6zujzBXFpCnqw6RSLSp9JSedDyu/V1DF4= 7 | github.com/orsinium-labs/wypes v0.1.4 h1:+7oih2IDlEpz7laiL3sQFlIU8vjd5j/SwcYlPdIPc0Q= 8 | github.com/orsinium-labs/wypes v0.1.4/go.mod h1:FSNWGo8I6/D5RYXMkCxyu71TXJAlwJGQUxgs4i6MAwo= 9 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 h1:sB7EW6YA2z8k4or+w8Tm2eo1wD/f4uQ31AffFfgRhvo= 10 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 11 | -------------------------------------------------------------------------------- /simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "time" 7 | 8 | "github.com/hybridgroup/mechanoid/engine" 9 | "github.com/hybridgroup/mechanoid/interp" 10 | "github.com/orsinium-labs/wypes" 11 | ) 12 | 13 | //go:embed modules/ping.wasm 14 | var wasmCode []byte 15 | 16 | func main() { 17 | time.Sleep(2 * time.Second) 18 | 19 | println("Mechanoid engine starting...") 20 | eng := engine.NewEngine() 21 | eng.UseInterpreter(interp.NewInterpreter()) 22 | 23 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 24 | if err := eng.Init(); err != nil { 25 | println(err.Error()) 26 | return 27 | } 28 | 29 | println("Defining host function...") 30 | modules := wypes.Modules{ 31 | "hosted": wypes.Module{ 32 | "pong": wypes.H0(pongFunc), 33 | }, 34 | } 35 | if err := eng.Interpreter.SetModules(modules); err != nil { 36 | println(err.Error()) 37 | return 38 | } 39 | 40 | println("Loading and running WASM code...") 41 | ins, err := eng.LoadAndRun(bytes.NewReader(wasmCode)) 42 | if err != nil { 43 | println(err.Error()) 44 | return 45 | } 46 | 47 | for { 48 | println("Calling ping...") 49 | if _, err := ins.Call("ping"); err != nil { 50 | println(err.Error()) 51 | } 52 | 53 | time.Sleep(1 * time.Second) 54 | } 55 | } 56 | 57 | func pongFunc() wypes.Void { 58 | println("pong") 59 | return wypes.Void{} 60 | } 61 | -------------------------------------------------------------------------------- /simple/modules/ping.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/simple/modules/ping.wasm -------------------------------------------------------------------------------- /simple/modules/ping/README.md: -------------------------------------------------------------------------------- 1 | # Ping 2 | 3 | Exports a `ping()` function, that immediately calls the host's `pong()` function. 4 | 5 | Mechanoid WebAssembly module template. 6 | 7 | ## Building 8 | -------------------------------------------------------------------------------- /simple/modules/ping/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/simple/modules/ping 2 | 3 | go 1.22.0 4 | -------------------------------------------------------------------------------- /simple/modules/ping/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport hosted pong 6 | func pong() 7 | 8 | //go:export ping 9 | func ping() { 10 | pong() 11 | } 12 | 13 | func main() {} 14 | -------------------------------------------------------------------------------- /simple/modules/ping/ping.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mno-bulk-memory", 15 | "-mnontrapping-fptoint", 16 | "-msign-ext" 17 | ], 18 | "ldflags": [ 19 | "--stack-first", 20 | "--no-demangle", 21 | "--no-entry", 22 | "--import-memory", 23 | "--initial-memory=65536", 24 | "--max-memory=65536", 25 | "-zstack-size=4096" 26 | ], 27 | "extra-files": [ 28 | "src/runtime/asm_tinygowasm.S" 29 | ], 30 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 31 | } 32 | -------------------------------------------------------------------------------- /thumby/README.md: -------------------------------------------------------------------------------- 1 | # Thumby 2 | 3 | ![Thumby](../images/thumby.jpg) 4 | 5 | This is an example of an application built using Mechanoid specifically for the Thumby "itty-bitty game system". 6 | 7 | ## How it works 8 | 9 | ```mermaid 10 | flowchart LR 11 | subgraph Microcontroller 12 | subgraph Application 13 | Pong 14 | end 15 | subgraph ping.wasm 16 | Ping 17 | end 18 | subgraph Display 19 | ShowMessage 20 | end 21 | Ping-->Pong 22 | Application-->Ping 23 | Application-->ShowMessage 24 | Pong-->ShowMessage 25 | end 26 | ``` 27 | -------------------------------------------------------------------------------- /thumby/devices/display/display.go: -------------------------------------------------------------------------------- 1 | package display 2 | 3 | import ( 4 | "machine" 5 | 6 | "image/color" 7 | 8 | "github.com/orsinium-labs/wypes" 9 | "tinygo.org/x/drivers/ssd1306" 10 | "tinygo.org/x/tinyfont" 11 | "tinygo.org/x/tinyfont/proggy" 12 | ) 13 | 14 | var white = color.RGBA{255, 255, 255, 255} 15 | 16 | type Device struct { 17 | display ssd1306.Device 18 | } 19 | 20 | func (d *Device) Modules() wypes.Modules { 21 | return wypes.Modules{} 22 | } 23 | 24 | func (d *Device) Init() error { 25 | machine.SPI0.Configure(machine.SPIConfig{}) 26 | display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN) 27 | display.Configure(ssd1306.Config{ 28 | Width: 72, 29 | Height: 40, 30 | ResetCol: ssd1306.ResetValue{28, 99}, 31 | ResetPage: ssd1306.ResetValue{0, 5}, 32 | }) 33 | 34 | display.ClearDisplay() 35 | d.display = display 36 | 37 | return nil 38 | } 39 | 40 | func (d *Device) Clear() { 41 | d.display.ClearDisplay() 42 | } 43 | 44 | func (d *Device) ShowMessage(x, y int, msg string) { 45 | tinyfont.WriteLine(&d.display, &proggy.TinySZ8pt7b, int16(x), int16(y), msg, white) 46 | d.display.Display() 47 | } 48 | -------------------------------------------------------------------------------- /thumby/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/thumby 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/hybridgroup/mechanoid v0.2.0 7 | github.com/orsinium-labs/wypes v0.1.4 8 | tinygo.org/x/drivers v0.27.0 9 | tinygo.org/x/tinyfont v0.4.0 10 | ) 11 | 12 | require ( 13 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 14 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 15 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /thumby/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 2 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 3 | github.com/hybridgroup/mechanoid v0.2.0 h1:2Kg3c0cK9JK8ZeJkiN+l2o4B0QJYnNHkNEKraMFOugQ= 4 | github.com/hybridgroup/mechanoid v0.2.0/go.mod h1:7jQEJ0blXXbIcqmu2/d7vSxWL5Dxbt18zZirJ35OeXc= 5 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 h1:pP916/7OLromi+7fHcbw5jIXOT/idiEBQ3ucbl8caz8= 6 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834/go.mod h1:rLavUo4P0xVcDeDnViYEpPQPoACmp1py9UTLPY/R7Lg= 7 | github.com/orsinium-labs/tinytest v1.0.0 h1:YiGm/whlGm3mn/ynx9CCFuvEa3Q6yEGrzrKXEqJOkdc= 8 | github.com/orsinium-labs/tinytest v1.0.0/go.mod h1:GwcYBp0aKi6zujzBXFpCnqw6RSLSp9JSedDyu/V1DF4= 9 | github.com/orsinium-labs/wypes v0.1.4 h1:+7oih2IDlEpz7laiL3sQFlIU8vjd5j/SwcYlPdIPc0Q= 10 | github.com/orsinium-labs/wypes v0.1.4/go.mod h1:FSNWGo8I6/D5RYXMkCxyu71TXJAlwJGQUxgs4i6MAwo= 11 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 h1:sB7EW6YA2z8k4or+w8Tm2eo1wD/f4uQ31AffFfgRhvo= 12 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 13 | tinygo.org/x/drivers v0.27.0 h1:TEGk1lQvEhXxfvpEhUu+pwmCnhtldPI+hpHlO9VYixI= 14 | tinygo.org/x/drivers v0.27.0/go.mod h1:q/mU8G/wz821p8xXqbkBACOlmZFDHXd//DnYnCW+dDQ= 15 | tinygo.org/x/tinyfont v0.4.0 h1:XexPKEKiHInf6p4CMCJwsIheVPY0T46HUs6ictYyZfE= 16 | tinygo.org/x/tinyfont v0.4.0/go.mod h1:7nVj3j3geqBoPDzpFukAhF1C8AP9YocMsZy0HSAcGCA= 17 | -------------------------------------------------------------------------------- /thumby/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "time" 7 | 8 | "github.com/hybridgroup/mechanoid-examples/thumby/devices/display" 9 | "github.com/hybridgroup/mechanoid/convert" 10 | "github.com/hybridgroup/mechanoid/engine" 11 | "github.com/hybridgroup/mechanoid/interp" 12 | "github.com/orsinium-labs/wypes" 13 | ) 14 | 15 | //go:embed modules/ping.wasm 16 | var wasmCode []byte 17 | 18 | var ( 19 | eng *engine.Engine 20 | 21 | pingcount, pongcount int 22 | ) 23 | 24 | func main() { 25 | time.Sleep(3 * time.Second) 26 | 27 | println("Mechanoid engine starting...") 28 | eng = engine.NewEngine() 29 | eng.UseInterpreter(interp.NewInterpreter()) 30 | 31 | disp := &display.Device{} 32 | eng.AddDevice(disp) 33 | 34 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 35 | if err := eng.Init(); err != nil { 36 | println(err.Error()) 37 | return 38 | } 39 | 40 | println("Defining host function...") 41 | modules := wypes.Modules{ 42 | "hosted": wypes.Module{ 43 | "pong": wypes.H0(pongFunc), 44 | }, 45 | } 46 | if err := eng.Interpreter.SetModules(modules); err != nil { 47 | println(err.Error()) 48 | return 49 | } 50 | 51 | println("Loading and running WASM code...") 52 | ins, err := eng.LoadAndRun(bytes.NewReader(wasmCode)) 53 | if err != nil { 54 | println(err.Error()) 55 | return 56 | } 57 | 58 | for { 59 | pingcount++ 60 | println("Calling ping", pingcount) 61 | 62 | disp.Clear() 63 | disp.ShowMessage(5, 10, "ping "+convert.IntToString(pingcount)) 64 | 65 | if _, err := ins.Call("ping"); err != nil { 66 | println(err.Error()) 67 | } 68 | 69 | time.Sleep(1 * time.Second) 70 | } 71 | } 72 | 73 | func pongFunc() wypes.Void { 74 | pongcount++ 75 | 76 | println("pong", pongcount) 77 | 78 | eng.Devices[0].(*display.Device).ShowMessage(5, 30, "pong "+convert.IntToString(pongcount)) 79 | 80 | return wypes.Void{} 81 | } 82 | -------------------------------------------------------------------------------- /thumby/modules/ping.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/thumby/modules/ping.wasm -------------------------------------------------------------------------------- /thumby/modules/ping/README.md: -------------------------------------------------------------------------------- 1 | # Ping 2 | 3 | Mechanoid WebAssembly module template. 4 | 5 | ## Building 6 | 7 | -------------------------------------------------------------------------------- /thumby/modules/ping/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/thumby/modules/ping 2 | 3 | go 1.22.0 4 | -------------------------------------------------------------------------------- /thumby/modules/ping/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport hosted pong 6 | func pong() 7 | 8 | //go:export ping 9 | func ping() { 10 | pong() 11 | } 12 | 13 | func main() {} 14 | -------------------------------------------------------------------------------- /thumby/modules/ping/ping.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mno-bulk-memory", 15 | "-mnontrapping-fptoint", 16 | "-msign-ext" 17 | ], 18 | "ldflags": [ 19 | "--stack-first", 20 | "--no-demangle", 21 | "--no-entry", 22 | "--import-memory", 23 | "--initial-memory=65536", 24 | "--max-memory=65536", 25 | "-zstack-size=4096" 26 | ], 27 | "extra-files": [ 28 | "src/runtime/asm_tinygowasm.S" 29 | ], 30 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 31 | } 32 | -------------------------------------------------------------------------------- /thumbyfile/README.md: -------------------------------------------------------------------------------- 1 | # Thumby Filestore 2 | 3 | Application that demonstrates how to use the onboard Flash storage on the Thumby device to save/load/run external WASM modules via a Command line interface directly on the device itself, along with display support so you can see what it happening on the tiny display. 4 | 5 | ## How to run 6 | 7 | ### Flash the board 8 | 9 | ```bash 10 | $ mecha flash -i wasman -m thumby 11 | Building TinyGo module ping 12 | Done. 13 | code data bss | flash ram 14 | 9 0 0 | 9 0 15 | Building Rust module pingrs 16 | Done. 17 | warning: unstable feature specified for `-Ctarget-feature`: `atomics` 18 | | 19 | = note: this feature is not stably supported; its behavior can change in the future 20 | warning: unstable feature specified for `-Ctarget-feature`: `bulk-memory` 21 | | 22 | = note: this feature is not stably supported; its behavior can change in the future 23 | warning: `pingrs` (lib) generated 2 warnings 24 | Finished release [optimized] target(s) in 0.00s 25 | Building Zig module pingzig 26 | Done. 27 | Application built. Now flashing... 28 | code data bss | flash ram 29 | 147716 4 3640 | 147720 3644 30 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 31 | 32 | ==> 33 | ``` 34 | 35 | You should see the `==>` prompt. See "How to use" below. 36 | 37 | ## How to use 38 | 39 | You should see a `==>` prompt. Try the `lsblk` command to see the Flash storage information: 40 | 41 | ```bash 42 | ==> lsblk 43 | ------------------------------------- 44 | Device Information: 45 | ------------------------------------- 46 | flash data start: 0x00024000 47 | flash data end: 0x00080000 48 | ------------------------------------- 49 | ``` 50 | 51 | This the the available Flash memory on your board in the extra space not being used by your program. 52 | 53 | Try the `ls` command. 54 | 55 | ```bash 56 | ==> ls 57 | 58 | ------------------------------------- 59 | File Store: 60 | ------------------------------------- 61 | 62 | ------------------------------------- 63 | ``` 64 | 65 | You do not yet have any WASM files in the Flash storage. Let's put one on the device using the `save` command. 66 | 67 | The easiest way to do this is the included `savefile` program. Press `CTRL-C` to return to your shell, then run the following command (substitute the correct port name for `/dev/ttyACM0` as needed): 68 | 69 | ```bash 70 | cd ./filestore 71 | 72 | go run ./savefile ./modules/ping.wasm /dev/ttyACM0 73 | ``` 74 | 75 | Now connect again to the board, and now you should see the file listed using the `ls` command: 76 | 77 | ```bash 78 | $ mecha monitor 79 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 80 | 81 | ==> ls 82 | 83 | ------------------------------------- 84 | File Store: 85 | ------------------------------------- 86 | 370 ping.wasm 87 | 88 | ------------------------------------- 89 | ``` 90 | 91 | You can now load the module: 92 | 93 | ```bash 94 | ==> load ping.wasm 95 | loading ping.wasm 96 | module loaded. 97 | ``` 98 | 99 | And then start it running: 100 | 101 | ```bash 102 | ==> run 103 | module running. 104 | ``` 105 | 106 | Use the `ping` command: 107 | 108 | ```bash 109 | ==> ping 3 110 | Ping... 111 | pong 112 | Ping... 113 | pong 114 | Ping... 115 | pong 116 | ``` 117 | 118 | Use the `halt` command to stop the module. 119 | 120 | ```bash 121 | ==> halt 122 | halting... 123 | module halted. 124 | ``` 125 | 126 | You can load another module now. Let's try one written using Rust. 127 | 128 | First, transfer the compiled pingrs.wasm module to the board. Press `CTRL-C` to return to your shell, then run the following command: 129 | 130 | ```bash 131 | go run ./savefile ./modules/pingrs.wasm /dev/ttyACM0 132 | ``` 133 | 134 | Now connect again to the board, and now you should see the file listed using the `ls` command, alongside the previously saved file: 135 | 136 | ```bash 137 | $ mecha monitor 138 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 139 | 140 | ==> ls 141 | 142 | ------------------------------------- 143 | File Store: 144 | ------------------------------------- 145 | 370 ping.wasm 146 | 324 pingrs.wasm 147 | ------------------------------------- 148 | ``` 149 | 150 | You can now load the Rust module: 151 | 152 | ```bash 153 | ==> load pingrs.wasm 154 | loading pingrs.wasm 155 | module loaded. 156 | ``` 157 | 158 | start it running: 159 | 160 | ```bash 161 | ==> run 162 | module running. 163 | ``` 164 | 165 | And use the `ping` command to call the Rust module: 166 | 167 | ```bash 168 | ==> ping 3 169 | Ping... 170 | pong 171 | Ping... 172 | pong 173 | Ping... 174 | pong 175 | ``` 176 | -------------------------------------------------------------------------------- /thumbyfile/baremetal.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | 8 | "github.com/hybridgroup/mechanoid/filestore/flash" 9 | ) 10 | 11 | var ( 12 | console = machine.Serial 13 | fs = &flash.FileStore{} 14 | ) 15 | 16 | func dataStart() uint32 { 17 | return uint32(machine.FlashDataStart()) 18 | } 19 | 20 | func dataEnd() uint32 { 21 | return uint32(machine.FlashDataEnd()) 22 | } 23 | 24 | func clearDisplay() { 25 | disp.Clear() 26 | } 27 | 28 | func displayMessage(x, y int, msg string) { 29 | disp.ShowMessage(x, y, msg) 30 | } 31 | -------------------------------------------------------------------------------- /thumbyfile/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "runtime" 6 | "time" 7 | 8 | "encoding/binary" 9 | "encoding/hex" 10 | "strings" 11 | 12 | "github.com/hybridgroup/mechanoid/convert" 13 | "github.com/hybridgroup/mechanoid/engine" 14 | ) 15 | 16 | const consoleBufLen = 64 17 | 18 | const ( 19 | StateInput = iota 20 | StateEscape 21 | StateEscBrc 22 | StateCSI 23 | ) 24 | 25 | var ( 26 | input [consoleBufLen]byte 27 | state = StateInput 28 | 29 | commands = map[string]cmdfunc{ 30 | "": noop, 31 | "ls": ls, 32 | "lsblk": lsblk, 33 | "load": load, 34 | "save": save, 35 | "rm": rm, 36 | "run": run, 37 | "halt": halt, 38 | "ping": ping, 39 | } 40 | ) 41 | 42 | func cli() { 43 | prompt() 44 | clearDisplay() 45 | displayMessage(2, 10, "ready") 46 | 47 | for i := 0; ; { 48 | if console.Buffered() > 0 { 49 | data, _ := console.ReadByte() 50 | switch state { 51 | case StateInput: 52 | switch data { 53 | case 0x8: 54 | fallthrough 55 | case 0x7f: // this is probably wrong... works on my machine tho :) 56 | // backspace 57 | if i > 0 { 58 | i -= 1 59 | console.Write([]byte{0x8, 0x20, 0x8}) 60 | } 61 | case 13: 62 | // return key 63 | if console.Buffered() > 0 { 64 | data, _ := console.ReadByte() 65 | if data != 10 { 66 | println("\r\nunexpected: \r", int(data)) 67 | } 68 | } 69 | console.Write([]byte("\r\n")) 70 | runCommand(string(input[:i])) 71 | prompt() 72 | 73 | i = 0 74 | continue 75 | case 27: 76 | // escape 77 | state = StateEscape 78 | default: 79 | // anything else, just echo the character if it is printable 80 | if i < (consoleBufLen - 1) { 81 | console.WriteByte(data) 82 | input[i] = data 83 | i++ 84 | } 85 | } 86 | case StateEscape: 87 | switch data { 88 | case 0x5b: 89 | state = StateEscBrc 90 | default: 91 | state = StateInput 92 | } 93 | default: 94 | // TODO: handle escape sequences 95 | state = StateInput 96 | } 97 | } 98 | time.Sleep(10 * time.Millisecond) 99 | } 100 | } 101 | 102 | func runCommand(line string) { 103 | defer func() { 104 | p := recover() 105 | if p != nil { 106 | println("panic:", p) 107 | } 108 | }() 109 | 110 | argv := strings.SplitN(strings.TrimSpace(line), " ", -1) 111 | cmd := argv[0] 112 | cmdfn, ok := commands[cmd] 113 | if !ok { 114 | println("unknown command: " + line) 115 | return 116 | } 117 | cmdfn(argv) 118 | } 119 | 120 | func prompt() { 121 | print("==> ") 122 | } 123 | 124 | type cmdfunc func(argv []string) 125 | 126 | func noop(argv []string) {} 127 | 128 | func ls(argv []string) { 129 | if eng.FileStore == nil { 130 | println("no file store available") 131 | return 132 | } 133 | 134 | list, err := eng.FileStore.List() 135 | if err != nil { 136 | println("error listing files:", err.Error()) 137 | return 138 | } 139 | 140 | print( 141 | "\n-------------------------------------\r\n" + 142 | " File Store: \r\n" + 143 | "-------------------------------------\r\n") 144 | for _, file := range list { 145 | println(file.Size(), file.Name()) 146 | } 147 | print( 148 | "\n-------------------------------------\r\n\r\n") 149 | } 150 | 151 | func lsblk(argv []string) { 152 | b := make([]byte, 4) 153 | binary.BigEndian.PutUint32(b, uint32(dataStart())) 154 | start := hex.EncodeToString(b) 155 | 156 | binary.BigEndian.PutUint32(b, uint32(dataEnd())) 157 | end := hex.EncodeToString(b) 158 | 159 | print( 160 | "\n-------------------------------------\r\n" + 161 | " Device Information: \r\n" + 162 | "-------------------------------------\r\n" + 163 | " flash data start: 0x" + start + "\r\n" + 164 | " flash data end: 0x" + end + "\r\n" + 165 | "-------------------------------------\r\n\r\n") 166 | } 167 | 168 | // load module into engine 169 | func load(argv []string) { 170 | if eng.FileStore == nil { 171 | println("no file store available") 172 | return 173 | } 174 | 175 | if len(argv) != 2 { 176 | println("usage: save ") 177 | return 178 | } 179 | 180 | if running { 181 | println("already running. halt first.") 182 | return 183 | } 184 | 185 | println("loading", argv[1]) 186 | clearDisplay() 187 | displayMessage(2, 10, "loading") 188 | displayMessage(2, 30, argv[1]) 189 | 190 | n, err := eng.FileStore.FileSize(argv[1]) 191 | if err != nil { 192 | println("error loading file:", err.Error()) 193 | return 194 | } 195 | 196 | data := make([]byte, n) 197 | if err := eng.FileStore.Load(argv[1], data); err != nil { 198 | println("error loading file:", err.Error()) 199 | return 200 | } 201 | 202 | if err := eng.Interpreter.Load(bytes.NewReader(data)); err != nil { 203 | println(err.Error()) 204 | return 205 | } 206 | println("module loaded.") 207 | clearDisplay() 208 | displayMessage(2, 10, argv[1]) 209 | displayMessage(2, 30, "loaded.") 210 | } 211 | 212 | // save into filestore 213 | func save(argv []string) { 214 | if eng.FileStore == nil { 215 | println("no file store available") 216 | return 217 | } 218 | 219 | if len(argv) != 3 { 220 | println("usage: save ") 221 | return 222 | } 223 | 224 | clearDisplay() 225 | displayMessage(2, 10, "saving") 226 | displayMessage(2, 30, argv[1]) 227 | 228 | // read in size bytes from port 229 | sz := convert.StringToInt(argv[2]) 230 | 231 | data := make([]byte, sz) 232 | if err := readDataFromPort(data); err != nil { 233 | println("error reading data:", err.Error()) 234 | return 235 | } 236 | 237 | if err := eng.FileStore.Save(argv[1], data); err != nil { 238 | println("error saving file:", err.Error()) 239 | } 240 | 241 | clearDisplay() 242 | displayMessage(2, 10, argv[1]) 243 | displayMessage(2, 30, "saved.") 244 | } 245 | 246 | func readDataFromPort(data []byte) (err error) { 247 | for i := 0; i < len(data); i++ { 248 | for console.Buffered() == 0 { 249 | } 250 | data[i], err = console.ReadByte() 251 | if err != nil { 252 | return 253 | } 254 | } 255 | return nil 256 | } 257 | 258 | // remove from filestore 259 | func rm(argv []string) { 260 | if eng.FileStore == nil { 261 | println("no file store available") 262 | return 263 | } 264 | 265 | if len(argv) != 2 { 266 | println("usage: rm ") 267 | return 268 | } 269 | 270 | clearDisplay() 271 | displayMessage(2, 10, "removing") 272 | displayMessage(2, 30, argv[1]) 273 | 274 | if err := eng.FileStore.Remove(argv[1]); err != nil { 275 | println("error removing file:", err.Error()) 276 | return 277 | } 278 | 279 | println(argv[1], "deleted.") 280 | clearDisplay() 281 | displayMessage(2, 10, argv[1]) 282 | displayMessage(2, 30, "deleted.") 283 | } 284 | 285 | var ( 286 | instance engine.Instance 287 | running bool 288 | ) 289 | 290 | func run(argv []string) { 291 | if eng.FileStore == nil { 292 | println("no file store available") 293 | return 294 | } 295 | 296 | if running { 297 | println("module already running. run 'halt' first.") 298 | return 299 | } 300 | 301 | // run the module 302 | var err error 303 | instance, err = eng.Interpreter.Run() 304 | if err != nil { 305 | println(err.Error()) 306 | return 307 | } 308 | 309 | running = true 310 | println("module running.") 311 | clearDisplay() 312 | displayMessage(2, 10, "module") 313 | displayMessage(2, 30, "running.") 314 | } 315 | 316 | func halt(argv []string) { 317 | if !running { 318 | println("module not running") 319 | return 320 | } 321 | 322 | println("halting...") 323 | _ = eng.Interpreter.Halt() 324 | instance = nil 325 | running = false 326 | runtime.GC() 327 | println("module halted.") 328 | clearDisplay() 329 | displayMessage(2, 10, "module") 330 | displayMessage(2, 30, "halted.") 331 | } 332 | 333 | func ping(argv []string) { 334 | if eng.FileStore == nil { 335 | println("no file store available") 336 | return 337 | } 338 | 339 | if !running { 340 | println("module not running. use 'run' first.") 341 | return 342 | } 343 | 344 | if len(argv) < 2 { 345 | println("usage: ping ") 346 | return 347 | } 348 | count := convert.StringToInt(argv[1]) 349 | 350 | clearDisplay() 351 | displayMessage(2, 10, "ping "+argv[1]) 352 | 353 | for i := 0; i < count; i++ { 354 | println("Ping...") 355 | if _, err := instance.Call("ping"); err != nil { 356 | println(err.Error()) 357 | return 358 | } 359 | } 360 | 361 | displayMessage(2, 30, "done.") 362 | } 363 | -------------------------------------------------------------------------------- /thumbyfile/devices/display/display.go: -------------------------------------------------------------------------------- 1 | package display 2 | 3 | import ( 4 | "machine" 5 | 6 | "image/color" 7 | 8 | "github.com/orsinium-labs/wypes" 9 | "tinygo.org/x/drivers/ssd1306" 10 | "tinygo.org/x/tinyfont" 11 | "tinygo.org/x/tinyfont/proggy" 12 | ) 13 | 14 | var white = color.RGBA{255, 255, 255, 255} 15 | 16 | type Device struct { 17 | display ssd1306.Device 18 | } 19 | 20 | func (d *Device) Modules() wypes.Modules { 21 | return wypes.Modules{} 22 | } 23 | 24 | func (d *Device) Init() error { 25 | machine.SPI0.Configure(machine.SPIConfig{}) 26 | display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN) 27 | display.Configure(ssd1306.Config{ 28 | Width: 72, 29 | Height: 40, 30 | ResetCol: ssd1306.ResetValue{28, 99}, 31 | ResetPage: ssd1306.ResetValue{0, 5}, 32 | }) 33 | 34 | display.ClearDisplay() 35 | d.display = display 36 | 37 | return nil 38 | } 39 | 40 | func (d *Device) Clear() { 41 | d.display.ClearDisplay() 42 | } 43 | 44 | func (d *Device) ShowMessage(x, y int, msg string) { 45 | tinyfont.WriteLine(&d.display, &proggy.TinySZ8pt7b, int16(x), int16(y), msg, white) 46 | d.display.Display() 47 | } 48 | -------------------------------------------------------------------------------- /thumbyfile/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/filestore 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/hybridgroup/mechanoid v0.2.0 7 | github.com/orsinium-labs/wypes v0.1.4 8 | go.bug.st/serial v1.6.2 9 | tinygo.org/x/drivers v0.27.1-0.20240401054046-a74770b2e6fe 10 | tinygo.org/x/tinyfont v0.3.0 11 | ) 12 | 13 | require ( 14 | github.com/creack/goselect v0.1.2 // indirect 15 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 16 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 17 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 18 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect 19 | tinygo.org/x/tinyfs v0.3.1-0.20231212053859-32ae3f6bbad9 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /thumbyfile/go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= 2 | github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 6 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 7 | github.com/hybridgroup/mechanoid v0.2.0 h1:2Kg3c0cK9JK8ZeJkiN+l2o4B0QJYnNHkNEKraMFOugQ= 8 | github.com/hybridgroup/mechanoid v0.2.0/go.mod h1:7jQEJ0blXXbIcqmu2/d7vSxWL5Dxbt18zZirJ35OeXc= 9 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 h1:pP916/7OLromi+7fHcbw5jIXOT/idiEBQ3ucbl8caz8= 10 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834/go.mod h1:rLavUo4P0xVcDeDnViYEpPQPoACmp1py9UTLPY/R7Lg= 11 | github.com/orsinium-labs/tinytest v1.0.0 h1:YiGm/whlGm3mn/ynx9CCFuvEa3Q6yEGrzrKXEqJOkdc= 12 | github.com/orsinium-labs/tinytest v1.0.0/go.mod h1:GwcYBp0aKi6zujzBXFpCnqw6RSLSp9JSedDyu/V1DF4= 13 | github.com/orsinium-labs/wypes v0.1.4 h1:+7oih2IDlEpz7laiL3sQFlIU8vjd5j/SwcYlPdIPc0Q= 14 | github.com/orsinium-labs/wypes v0.1.4/go.mod h1:FSNWGo8I6/D5RYXMkCxyu71TXJAlwJGQUxgs4i6MAwo= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 18 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 19 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 h1:sB7EW6YA2z8k4or+w8Tm2eo1wD/f4uQ31AffFfgRhvo= 20 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 21 | go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8= 22 | go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= 23 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= 24 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 25 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 26 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | tinygo.org/x/drivers v0.27.1-0.20240401054046-a74770b2e6fe h1:OLrF872e3ow7nAW5HIa2CD1IHTP4EyNIiAxg5k8HtfU= 28 | tinygo.org/x/drivers v0.27.1-0.20240401054046-a74770b2e6fe/go.mod h1:q/mU8G/wz821p8xXqbkBACOlmZFDHXd//DnYnCW+dDQ= 29 | tinygo.org/x/tinyfont v0.3.0 h1:HIRLQoI3oc+2CMhPcfv+Ig88EcTImE/5npjqOnMD4lM= 30 | tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= 31 | tinygo.org/x/tinyfs v0.3.1-0.20231212053859-32ae3f6bbad9 h1:u3w9x/Jo4A5Q+WxABVd15dD2ScTI7B6DmGb3lTKNP/w= 32 | tinygo.org/x/tinyfs v0.3.1-0.20231212053859-32ae3f6bbad9/go.mod h1:smxJYYJRbfvbQofdYoECIBPHQZJTKd4YeS3ZLvx+lpQ= 33 | -------------------------------------------------------------------------------- /thumbyfile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "time" 6 | 7 | "github.com/hybridgroup/mechanoid-examples/filestore/devices/display" 8 | "github.com/hybridgroup/mechanoid/engine" 9 | "github.com/hybridgroup/mechanoid/interp" 10 | "github.com/orsinium-labs/wypes" 11 | ) 12 | 13 | var ( 14 | eng *engine.Engine 15 | disp *display.Device 16 | 17 | pingcount, pongcount int 18 | ) 19 | 20 | func main() { 21 | time.Sleep(1 * time.Second) 22 | 23 | println("Mechanoid engine starting...") 24 | eng = engine.NewEngine() 25 | eng.UseInterpreter(interp.NewInterpreter()) 26 | eng.UseFileStore(fs) 27 | 28 | disp = &display.Device{} 29 | eng.AddDevice(disp) 30 | 31 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 32 | if err := eng.Init(); err != nil { 33 | println(err.Error()) 34 | return 35 | } 36 | 37 | modules := wypes.Modules{ 38 | "hosted": wypes.Module{ 39 | "pong": wypes.H0(pongFunc), 40 | }, 41 | } 42 | if err := eng.Interpreter.SetModules(modules); err != nil { 43 | println(err.Error()) 44 | return 45 | } 46 | // start up CLI 47 | cli() 48 | } 49 | 50 | func pongFunc() wypes.Void { 51 | println("pong") 52 | return wypes.Void{} 53 | } 54 | -------------------------------------------------------------------------------- /thumbyfile/modules/ping.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/thumbyfile/modules/ping.wasm -------------------------------------------------------------------------------- /thumbyfile/modules/ping/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) The Hybrid Group 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | --- LLVM Exceptions to the Apache 2.0 License ---- 205 | 206 | As an exception, if, as a result of your compiling your source code, portions 207 | of this Software are embedded into an Object form of such source code, you 208 | may redistribute such embedded portions in such Object form without complying 209 | with the conditions of Sections 4(a), 4(b) and 4(d) of the License. 210 | 211 | In addition, if you combine or link compiled forms of this Software with 212 | software that is licensed under the GPLv2 ("Combined Software") and if a 213 | court of competent jurisdiction determines that the patent provision (Section 214 | 3), the indemnity provision (Section 9) or other Section of the License 215 | conflicts with the conditions of the GPLv2, you may retroactively and 216 | prospectively choose to deem waived or otherwise exclude such Section(s) of 217 | the License, but only in their entirety and only with respect to the Combined 218 | Software. 219 | -------------------------------------------------------------------------------- /thumbyfile/modules/ping/README.md: -------------------------------------------------------------------------------- 1 | # Ping 2 | 3 | Exports a `ping()` function, that immediately calls the host's `pong()` function. 4 | 5 | Mechanoid WebAssembly module template. 6 | 7 | ## Building 8 | 9 | ``` 10 | mecha build 11 | ``` -------------------------------------------------------------------------------- /thumbyfile/modules/ping/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/filestore/modules/ping 2 | 3 | go 1.22.0 4 | 5 | replace github.com/tetratelabs/wazero => github.com/orsinium-forks/wazero v0.0.0-20240305131633-28fdf656fe85 6 | -------------------------------------------------------------------------------- /thumbyfile/modules/ping/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport hosted pong 6 | func pong() 7 | 8 | //go:export ping 9 | func ping() { 10 | pong() 11 | } 12 | 13 | func main() {} 14 | -------------------------------------------------------------------------------- /thumbyfile/modules/ping/ping.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mno-bulk-memory", 15 | "-mnontrapping-fptoint", 16 | "-msign-ext" 17 | ], 18 | "ldflags": [ 19 | "--stack-first", 20 | "--no-demangle", 21 | "--no-entry", 22 | "--import-memory", 23 | "--initial-memory=65536", 24 | "--max-memory=65536", 25 | "-zstack-size=4096" 26 | ], 27 | "extra-files": [ 28 | "src/runtime/asm_tinygowasm.S" 29 | ], 30 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 31 | } 32 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingrs.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/thumbyfile/modules/pingrs.wasm -------------------------------------------------------------------------------- /thumbyfile/modules/pingrs/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target."wasm32-unknown-unknown"] 2 | rustflags = ["-C", "target-feature=+atomics,+bulk-memory", 3 | "-C", "link-arg=--import-memory", 4 | "-C", "link-arg=--initial-memory=65536", 5 | "-C", "link-arg=--max-memory=65536", 6 | "-C", "link-arg=-zstack-size=4096"] 7 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingrs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingrs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "pingrs" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingrs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pingrs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | [lib] 11 | crate-type = ["cdylib"] 12 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingrs/README.md: -------------------------------------------------------------------------------- 1 | # pingrs 2 | 3 | WASM unknown module written in Rust 4 | 5 | ## How to install 6 | 7 | - Install Rust. 8 | 9 | - Install the `wasm32-unknown-unknown` target. 10 | 11 | ```bash 12 | rustup target add wasm32-unknown-unknown 13 | ``` 14 | 15 | ## How to build module 16 | 17 | ```bash 18 | cd modules/pingrs 19 | cargo build --target wasm32-unknown-unknown --release 20 | cd ../.. 21 | cp ./modules/pingrs/target/wasm32-unknown-unknown/release/pingrs.wasm ./modules/ 22 | ``` 23 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingrs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[panic_handler] 4 | fn handle_panic(_: &core::panic::PanicInfo) -> ! { 5 | core::arch::wasm32::unreachable() 6 | } 7 | 8 | #[no_mangle] 9 | pub extern fn ping() { 10 | unsafe {pong()}; 11 | } 12 | 13 | #[link(wasm_import_module = "hosted")] 14 | extern { 15 | fn pong(); 16 | } 17 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingzig.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/thumbyfile/modules/pingzig.wasm -------------------------------------------------------------------------------- /thumbyfile/modules/pingzig/.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out/ 3 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingzig/README.md: -------------------------------------------------------------------------------- 1 | # PingZig 2 | 3 | Mechanoid module template in Zig that exports a `ping()` function, that immediately calls the host's `pong()` function. 4 | 5 | ## Building 6 | 7 | ``` 8 | mecha build 9 | ``` 10 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingzig/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | const lib = b.addSharedLibrary(.{ 5 | .name = "pingzig", 6 | .root_source_file = .{ .path = "src/ping.zig" }, 7 | .target = .{ 8 | .cpu_arch = .wasm32, 9 | .os_tag = .freestanding, 10 | }, 11 | .optimize = .ReleaseSmall, 12 | .link_libc = true, 13 | }); 14 | lib.rdynamic = true; 15 | lib.stack_size = 4096; 16 | lib.import_memory = true; 17 | lib.initial_memory = 65536; 18 | lib.max_memory = 65536; 19 | 20 | b.installArtifact(lib); 21 | } 22 | -------------------------------------------------------------------------------- /thumbyfile/modules/pingzig/src/ping.zig: -------------------------------------------------------------------------------- 1 | extern "hosted" fn pong() void; 2 | 3 | pub export fn ping() void { 4 | pong(); 5 | } 6 | -------------------------------------------------------------------------------- /thumbyfile/os.go: -------------------------------------------------------------------------------- 1 | //go:build !tinygo 2 | 3 | package main 4 | 5 | import "github.com/hybridgroup/mechanoid/filestore/filesystem" 6 | 7 | var ( 8 | console = UART{} 9 | fs = &filesystem.FileStore{} 10 | ) 11 | 12 | type UARTConfig struct{} 13 | 14 | type UART struct { 15 | } 16 | 17 | // Configure the UART. 18 | func (uart *UART) Configure(config UARTConfig) { 19 | } 20 | 21 | // Read from the UART. 22 | func (uart *UART) Read(data []byte) (n int, err error) { 23 | return 0, nil 24 | } 25 | 26 | // Write to the UART. 27 | func (uart *UART) Write(data []byte) (n int, err error) { 28 | return 0, nil 29 | } 30 | 31 | // Buffered returns the number of bytes currently stored in the RX buffer. 32 | func (uart *UART) Buffered() int { 33 | return 0 34 | } 35 | 36 | // ReadByte reads a single byte from the UART. 37 | func (uart *UART) ReadByte() (byte, error) { 38 | return 0, nil 39 | } 40 | 41 | // WriteByte writes a single byte to the UART. 42 | func (uart *UART) WriteByte(b byte) error { 43 | return nil 44 | } 45 | 46 | func dataStart() uint32 { 47 | return 0 48 | } 49 | 50 | func dataEnd() uint32 { 51 | return 0 52 | } 53 | 54 | func clearDisplay() { 55 | return 56 | } 57 | 58 | func displayMessage(x, y int, msg string) { 59 | println(msg) 60 | } 61 | -------------------------------------------------------------------------------- /thumbyfile/savefile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "time" 9 | 10 | "go.bug.st/serial" 11 | ) 12 | 13 | func main() { 14 | if len(os.Args) < 2 { 15 | fmt.Printf("usage: %s path/to/xxx.wasm port", os.Args[0]) 16 | os.Exit(0) 17 | } 18 | err := run(os.Args[1], os.Args[2]) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | } 23 | 24 | func run(target, port string) error { 25 | p, err := serial.Open(port, &serial.Mode{}) 26 | if err != nil { 27 | return err 28 | } 29 | defer p.Close() 30 | p.SetReadTimeout(1 * time.Second) 31 | 32 | b, err := os.ReadFile(target) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | fmt.Fprintf(p, "\r\nsave %s %d\r\n", filepath.Base(target), len(b)) 38 | 39 | buf := make([]byte, 1024) 40 | for { 41 | n, err := p.Read(buf) 42 | if err != nil || n == 0 { 43 | break 44 | } 45 | } 46 | 47 | _, err = p.Write(b) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /wasmbadge/README.md: -------------------------------------------------------------------------------- 1 | # WASMBadge 2 | 3 | ![WASMBadge](../images/wasmbadge-pybadge.jpg) 4 | 5 | This application is a conference badge programmed using WASM. 6 | 7 | ## How it works 8 | 9 | ```mermaid 10 | flowchart LR 11 | subgraph Microcontroller 12 | subgraph Application 13 | end 14 | subgraph Devices 15 | subgraph Badge 16 | Heading 17 | SetText1 18 | SetText2 19 | end 20 | subgraph Display 21 | ShowMessage 22 | end 23 | end 24 | subgraph Modules 25 | subgraph hithere.wasm 26 | Start1[Start] 27 | Update1[Update] 28 | end 29 | subgraph mynameis.wasm 30 | Start2[Start] 31 | Update2[Update] 32 | end 33 | end 34 | Application-->Start1 35 | Application-->Update1 36 | Heading-->ShowMessage 37 | Start1-->Heading 38 | Update1-->SetText1 39 | Update1-->SetText2 40 | end 41 | ``` 42 | 43 | The application can connect to any of the display supported in the `boards` package. 44 | 45 | It embeds all of the WASM files in the `modules` directory right into the application itself. 46 | 47 | When the application runs, it presents a list of all of the different programs on the display. 48 | 49 | Use the buttons to choose one of the programs, and then press the "A" button to run it. 50 | 51 | If you want to cycle thru the entire list, press either the "START" button or the "B" button. The badge will run each of the WASM programs for 10 seconds before switching to the next one. 52 | 53 | To get back to the home screen, press either the "SELECT" button or the "B" button. 54 | 55 | ## How to run 56 | 57 | ### PyBadge 58 | 59 | ```bash 60 | $ mecha flash -i wasman pybadge 61 | Building module hithere 62 | Done. 63 | code data bss | flash ram 64 | 461 31 4097 | 492 4128 65 | Building module mynameis 66 | Done. 67 | code data bss | flash ram 68 | 93 32 4096 | 125 4128 69 | Building module mythingis 70 | Done. 71 | code data bss | flash ram 72 | 38 62 4096 | 100 4158 73 | Building module thisbadge 74 | Done. 75 | code data bss | flash ram 76 | 31 45 4096 | 76 4141 77 | Building module youarehere 78 | Done. 79 | code data bss | flash ram 80 | 24 18 4096 | 42 4114 81 | Application built. Now flashing... 82 | code data bss | flash ram 83 | 133032 1748 6792 | 134780 8540 84 | ``` 85 | 86 | ### Gopher Badge 87 | 88 | ```bash 89 | $ mecha flash -i wasman gopher-badge 90 | Building module hithere 91 | Done. 92 | code data bss | flash ram 93 | 461 31 4097 | 492 4128 94 | Building module mynameis 95 | Done. 96 | code data bss | flash ram 97 | 93 32 4096 | 125 4128 98 | Building module mythingis 99 | Done. 100 | code data bss | flash ram 101 | 38 62 4096 | 100 4158 102 | Building module thisbadge 103 | Done. 104 | code data bss | flash ram 105 | 31 45 4096 | 76 4141 106 | Building module youarehere 107 | Done. 108 | code data bss | flash ram 109 | 24 18 4096 | 42 4114 110 | Application built. Now flashing... 111 | code data bss | flash ram 112 | 143444 1752 3316 | 145196 5068 113 | ``` 114 | 115 | ### Simulator 116 | 117 | You need to install the Fyne cross-platform GUI toolkit to use the Mechanoid simulator. 118 | 119 | https://github.com/fyne-io/fyne 120 | 121 | 122 | ```bash 123 | $ mecha run -i wasman 124 | Running using interpreter wasman 125 | Building module hithere 126 | Done. 127 | code data bss | flash ram 128 | 461 31 4097 | 492 4128 129 | Building module mynameis 130 | Done. 131 | code data bss | flash ram 132 | 93 32 4096 | 125 4128 133 | Building module mythingis 134 | Done. 135 | code data bss | flash ram 136 | 38 55 4096 | 93 4151 137 | Building module thisbadge 138 | Done. 139 | code data bss | flash ram 140 | 31 45 4096 | 76 4141 141 | Building module youarehere 142 | Done. 143 | code data bss | flash ram 144 | 24 18 4096 | 42 4114 145 | Mechanoid engine starting... 146 | Initializing engine using interpreter wasman 147 | Registering host modules... 148 | ... 149 | ``` -------------------------------------------------------------------------------- /wasmbadge/devices/badge/badge.go: -------------------------------------------------------------------------------- 1 | package badge 2 | 3 | import ( 4 | "github.com/hybridgroup/mechanoid-examples/wasmbadge/devices/display" 5 | "github.com/orsinium-labs/wypes" 6 | "tinygo.org/x/drivers/pixel" 7 | ) 8 | 9 | type Badge[T pixel.Color] struct { 10 | Display *display.Device[T] 11 | bt *BigText[T] 12 | } 13 | 14 | func NewDevice[T pixel.Color](d *display.Device[T]) *Badge[T] { 15 | return &Badge[T]{ 16 | Display: d, 17 | } 18 | } 19 | 20 | func (b *Badge[T]) Init() error { 21 | b.bt = NewBigText[T](b.Display) 22 | b.bt.Show(b.Display) 23 | 24 | return nil 25 | } 26 | 27 | func (b *Badge[T]) Modules() wypes.Modules { 28 | return wypes.Modules{ 29 | "badge": wypes.Module{ 30 | "heading": wypes.H1(b.bigTextHeading), 31 | "set_text1": wypes.H1(b.bigTextSetText1), 32 | "set_text2": wypes.H1(b.bigTextSetText2), 33 | "set_text3": wypes.H1(b.bigTextSetText3), 34 | "set_text4": wypes.H1(b.bigTextSetText4), 35 | }, 36 | } 37 | } 38 | 39 | func (b *Badge[T]) Clear() error { 40 | b.bt.Heading("") 41 | b.bt.SetText1("") 42 | b.bt.SetText2("") 43 | b.bt.SetText3("") 44 | b.bt.SetText4("") 45 | b.bt.Show(b.Display) 46 | 47 | return nil 48 | } 49 | 50 | func (b *Badge[T]) bigTextHeading(msg wypes.String) wypes.Void { 51 | b.Heading(msg.Unwrap()) 52 | 53 | return wypes.Void{} 54 | } 55 | 56 | func (b *Badge[T]) bigTextSetText1(msg wypes.String) wypes.Void { 57 | b.SetText1(msg.Unwrap()) 58 | 59 | return wypes.Void{} 60 | } 61 | 62 | func (b *Badge[T]) bigTextSetText2(msg wypes.String) wypes.Void { 63 | b.SetText2(msg.Unwrap()) 64 | 65 | return wypes.Void{} 66 | } 67 | 68 | func (b *Badge[T]) bigTextSetText3(msg wypes.String) wypes.Void { 69 | b.SetText3(msg.Unwrap()) 70 | 71 | return wypes.Void{} 72 | } 73 | 74 | func (b *Badge[T]) bigTextSetText4(msg wypes.String) wypes.Void { 75 | b.SetText4(msg.Unwrap()) 76 | 77 | return wypes.Void{} 78 | } 79 | 80 | func (b *Badge[T]) Heading(msg string) { 81 | b.bt.Heading(msg) 82 | b.bt.Show(b.Display) 83 | b.Display.Screen.Update() 84 | } 85 | 86 | func (b *Badge[T]) SetText1(msg string) { 87 | b.bt.SetText1(msg) 88 | b.bt.Show(b.Display) 89 | b.Display.Screen.Update() 90 | } 91 | 92 | func (b *Badge[T]) SetText2(msg string) { 93 | b.bt.SetText2(msg) 94 | b.bt.Show(b.Display) 95 | b.Display.Screen.Update() 96 | } 97 | 98 | func (b *Badge[T]) SetText3(msg string) { 99 | b.bt.SetText3(msg) 100 | b.bt.Show(b.Display) 101 | b.Display.Screen.Update() 102 | } 103 | 104 | func (b *Badge[T]) SetText4(msg string) { 105 | b.bt.SetText4(msg) 106 | b.bt.Show(b.Display) 107 | b.Display.Screen.Update() 108 | } 109 | -------------------------------------------------------------------------------- /wasmbadge/devices/badge/bigtext.go: -------------------------------------------------------------------------------- 1 | package badge 2 | 3 | import ( 4 | "github.com/aykevl/tinygl" 5 | "tinygo.org/x/drivers/pixel" 6 | 7 | "github.com/hybridgroup/mechanoid-examples/wasmbadge/devices/display" 8 | ) 9 | 10 | type BigText[T pixel.Color] struct { 11 | Name string 12 | VBox *tinygl.VBox[T] 13 | Header *tinygl.Text[T] 14 | TextBox1 *tinygl.Text[T] 15 | TextBox2 *tinygl.Text[T] 16 | TextBox3 *tinygl.Text[T] 17 | TextBox4 *tinygl.Text[T] 18 | } 19 | 20 | // createWasmPage creates the screen when executing wasm on the badge. 21 | func NewBigText[T pixel.Color](d *display.Device[T]) *BigText[T] { 22 | if d == nil { 23 | return nil 24 | } 25 | 26 | header := d.Theme.NewText("Mechanoid") 27 | header.SetBackground(pixel.NewColor[T](255, 0, 0)) 28 | header.SetColor(pixel.NewColor[T](255, 255, 255)) 29 | 30 | textbox1 := d.Theme.NewText("") 31 | textbox1.SetAlign(tinygl.AlignCenter) 32 | textbox2 := d.Theme.NewText("") 33 | textbox2.SetAlign(tinygl.AlignCenter) 34 | textbox3 := d.Theme.NewText("") 35 | textbox3.SetAlign(tinygl.AlignCenter) 36 | textbox4 := d.Theme.NewText("") 37 | textbox4.SetAlign(tinygl.AlignCenter) 38 | 39 | vbox := d.Theme.NewVBox(header, textbox1, textbox2, textbox3, textbox4) 40 | 41 | return &BigText[T]{ 42 | Name: "BigText", 43 | VBox: vbox, 44 | Header: header, 45 | TextBox1: textbox1, 46 | TextBox2: textbox2, 47 | TextBox3: textbox3, 48 | TextBox4: textbox4, 49 | } 50 | } 51 | 52 | func (bt *BigText[T]) Show(d *display.Device[T]) { 53 | d.Screen.SetChild(bt.VBox) 54 | d.Screen.Update() 55 | } 56 | 57 | func (bt *BigText[T]) Heading(s string) { 58 | bt.Header.SetText(s) 59 | } 60 | 61 | func (bt *BigText[T]) SetText1(s string) { 62 | bt.TextBox1.SetText(s) 63 | } 64 | 65 | func (bt *BigText[T]) SetText2(s string) { 66 | bt.TextBox2.SetText(s) 67 | } 68 | 69 | func (bt *BigText[T]) SetText3(s string) { 70 | bt.TextBox3.SetText(s) 71 | } 72 | 73 | func (bt *BigText[T]) SetText4(s string) { 74 | bt.TextBox4.SetText(s) 75 | } 76 | -------------------------------------------------------------------------------- /wasmbadge/devices/display/display.go: -------------------------------------------------------------------------------- 1 | package display 2 | 3 | import ( 4 | "github.com/aykevl/board" 5 | "github.com/aykevl/tinygl" 6 | "github.com/aykevl/tinygl/style" 7 | "github.com/aykevl/tinygl/style/basic" 8 | "github.com/orsinium-labs/wypes" 9 | "tinygo.org/x/drivers/pixel" 10 | ) 11 | 12 | type Device[T pixel.Color] struct { 13 | Display board.Displayer[T] 14 | Screen *tinygl.Screen[T] 15 | Theme *basic.Basic[T] 16 | } 17 | 18 | func NewDevice[T pixel.Color](disp board.Displayer[T]) Device[T] { 19 | return Device[T]{ 20 | Display: disp, 21 | } 22 | } 23 | 24 | func (d *Device[T]) Init() error { 25 | // Determine size and scale of the screen. 26 | width, height := d.Display.Size() 27 | scalePercent := board.Display.PPI() * 100 / 120 28 | 29 | // Initialize the screen. 30 | buf := pixel.NewImage[T](int(width), int(height)/4) 31 | d.Screen = tinygl.NewScreen[T](d.Display, buf, board.Display.PPI()) 32 | d.Theme = basic.NewTheme(style.NewScale(scalePercent), d.Screen) 33 | 34 | board.Display.SetBrightness(board.Display.MaxBrightness()) 35 | 36 | return nil 37 | } 38 | 39 | func (d *Device[T]) Modules() wypes.Modules { 40 | return wypes.Modules{} 41 | } 42 | -------------------------------------------------------------------------------- /wasmbadge/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/wasmbadge 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/aykevl/board v0.0.0-20240106144210-80ca76f77def 7 | github.com/aykevl/tinygl v0.0.0-20240131130748-3033a2fd9182 8 | github.com/hybridgroup/mechanoid v0.2.0 9 | github.com/orsinium-labs/wypes v0.1.4 10 | tinygo.org/x/drivers v0.26.1-0.20231124130000-fef6564044f9 11 | ) 12 | 13 | require ( 14 | fyne.io/fyne/v2 v2.4.4 // indirect 15 | fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/fredbi/uri v1.0.0 // indirect 18 | github.com/fsnotify/fsnotify v1.6.0 // indirect 19 | github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect 20 | github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect 21 | github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect 22 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect 23 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect 24 | github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 // indirect 25 | github.com/go-text/typesetting v0.1.0 // indirect 26 | github.com/godbus/dbus/v5 v5.1.0 // indirect 27 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 28 | github.com/gopherjs/gopherjs v1.17.2 // indirect 29 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 30 | github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect 31 | github.com/pmezard/go-difflib v1.0.0 // indirect 32 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect 33 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect 34 | github.com/stretchr/testify v1.8.4 // indirect 35 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 36 | github.com/tevino/abool v1.2.0 // indirect 37 | github.com/yuin/goldmark v1.5.5 // indirect 38 | golang.org/x/image v0.11.0 // indirect 39 | golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect 40 | golang.org/x/net v0.17.0 // indirect 41 | golang.org/x/sys v0.13.0 // indirect 42 | golang.org/x/text v0.13.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /wasmbadge/home.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/aykevl/tinygl" 5 | "github.com/aykevl/tinygl/style/basic" 6 | "tinygo.org/x/drivers/pixel" 7 | 8 | "github.com/hybridgroup/mechanoid-examples/wasmbadge/devices/display" 9 | ) 10 | 11 | type HomePage[T pixel.Color] struct { 12 | Name string 13 | VBox *tinygl.VerticalScrollBox[T] 14 | Header *tinygl.Text[T] 15 | ListBox *basic.ListBox[T] 16 | } 17 | 18 | var ( 19 | menuChoices = make([]string, 0, 10) 20 | ) 21 | 22 | // createHome creates the home screen for the badge. 23 | func createHome[T pixel.Color](d *display.Device[T]) *HomePage[T] { 24 | // Create badge homescreen. 25 | header := d.Theme.NewText("WASM Badge") 26 | header.SetBackground(pixel.NewColor[T](255, 0, 0)) 27 | header.SetColor(pixel.NewColor[T](255, 255, 255)) 28 | listbox := d.Theme.NewListBox(menuChoices) 29 | listbox.SetGrowable(0, 1) // listbox fills the rest of the screen 30 | listbox.Select(0) // focus the first element 31 | home := tinygl.NewVerticalScrollBox[T](header, listbox, nil) 32 | return &HomePage[T]{ 33 | Name: "home", 34 | VBox: home, 35 | Header: header, 36 | ListBox: listbox, 37 | } 38 | } 39 | 40 | func (p *HomePage[T]) Show(d *display.Device[T]) { 41 | d.Screen.SetChild(p.VBox) 42 | d.Screen.Update() 43 | } 44 | 45 | func loadMenuChoices() error { 46 | modules, err := modules.ReadDir("modules") 47 | if err != nil { 48 | return err 49 | } 50 | 51 | for _, module := range modules { 52 | if module.IsDir() { 53 | continue 54 | } 55 | menuChoices = append(menuChoices, module.Name()) 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /wasmbadge/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | "runtime" 6 | "time" 7 | 8 | "github.com/aykevl/board" 9 | "github.com/hybridgroup/mechanoid" 10 | "github.com/hybridgroup/mechanoid/engine" 11 | "github.com/hybridgroup/mechanoid/interp" 12 | "tinygo.org/x/drivers/pixel" 13 | 14 | "github.com/hybridgroup/mechanoid-examples/wasmbadge/devices/badge" 15 | "github.com/hybridgroup/mechanoid-examples/wasmbadge/devices/display" 16 | ) 17 | 18 | //go:embed modules/*.wasm 19 | var modules embed.FS 20 | 21 | var ( 22 | eng *engine.Engine 23 | ) 24 | 25 | func main() { 26 | if board.Name == "simulator" { 27 | // Use the configuration for the Gopher Badge. 28 | board.Simulator.WindowWidth = 320 29 | board.Simulator.WindowHeight = 240 30 | board.Simulator.WindowPPI = 166 31 | board.Simulator.WindowDrawSpeed = time.Second * 16 / 62_500e3 // 62.5MHz, 16bpp 32 | } 33 | 34 | run(board.Display.Configure()) 35 | } 36 | 37 | func run[T pixel.Color](disp board.Displayer[T]) { 38 | time.Sleep(2 * time.Second) 39 | 40 | mechanoid.DebugMemory("start of program") 41 | 42 | println("Mechanoid engine starting...") 43 | eng = engine.NewEngine() 44 | eng.UseInterpreter(interp.NewInterpreter()) 45 | 46 | // host interface to display API 47 | d := display.NewDevice[T](disp) 48 | eng.AddDevice(&d) 49 | 50 | // host interface to badge API 51 | b := badge.NewDevice[T](&d) 52 | eng.AddDevice(b) 53 | 54 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 55 | if err := eng.Init(); err != nil { 56 | println(err.Error()) 57 | return 58 | } 59 | 60 | mechanoid.DebugMemory("after engine init") 61 | 62 | board.Buttons.Configure() 63 | 64 | loadMenuChoices() 65 | home := createHome[T](&d) 66 | home.Show(&d) 67 | 68 | listbox := home.ListBox 69 | 70 | for { 71 | board.Buttons.ReadInput() 72 | event := board.Buttons.NextEvent() 73 | if !event.Pressed() { 74 | continue 75 | } 76 | switch event.Key() { 77 | case board.KeyUp: 78 | index := listbox.Selected() - 1 79 | if index < 0 { 80 | index = listbox.Len() - 1 81 | } 82 | listbox.Select(index) 83 | case board.KeyDown: 84 | index := listbox.Selected() + 1 85 | if index >= listbox.Len() { 86 | index = 0 87 | } 88 | listbox.Select(index) 89 | case board.KeyA: 90 | home = nil 91 | runWASM(menuChoices[listbox.Selected()], &d, b) 92 | 93 | mechanoid.DebugMemory("after runWASM exit") 94 | runtime.GC() 95 | mechanoid.DebugMemory("after runWASM exit GC") 96 | 97 | home = createHome[T](&d) 98 | home.Show(&d) 99 | listbox = home.ListBox 100 | case board.KeyStart, board.KeyEnter, board.KeyB: 101 | // rotation 102 | home = nil 103 | runWASMRotation(&d, b) 104 | 105 | mechanoid.DebugMemory("after runWASM exit") 106 | runtime.GC() 107 | mechanoid.DebugMemory("after runWASM exit GC") 108 | 109 | home = createHome[T](&d) 110 | home.Show(&d) 111 | listbox = home.ListBox 112 | } 113 | 114 | d.Screen.Update() 115 | time.Sleep(time.Second / 30) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /wasmbadge/modules/hithere.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/wasmbadge/modules/hithere.wasm -------------------------------------------------------------------------------- /wasmbadge/modules/hithere/README.md: -------------------------------------------------------------------------------- 1 | # Hi There 2 | 3 | Mechanoid WebAssembly module template. 4 | 5 | ## Building 6 | 7 | -------------------------------------------------------------------------------- /wasmbadge/modules/hithere/badge.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport badge heading 6 | func badge_heading(ptr, sz uint32) 7 | 8 | //go:wasmimport badge set_text1 9 | func badge_set_text1(ptr, sz uint32) 10 | 11 | //go:wasmimport badge set_text2 12 | func badge_set_text2(ptr, sz uint32) 13 | 14 | //go:wasmimport badge set_text3 15 | func badge_set_text3(ptr, sz uint32) 16 | 17 | //go:wasmimport badge set_text4 18 | func badge_set_text4(ptr, sz uint32) 19 | -------------------------------------------------------------------------------- /wasmbadge/modules/hithere/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/wasmbadge/modules/mynameis 2 | 3 | go 1.22.0 4 | 5 | require github.com/hybridgroup/mechanoid v0.0.0-20240308111911-97652132727d 6 | -------------------------------------------------------------------------------- /wasmbadge/modules/hithere/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.0.0-20240308111911-97652132727d h1:KRCQ1daf3LiWzL0nIxzWNdHmDFLnwLFGhCmuEmyR1cA= 2 | github.com/hybridgroup/mechanoid v0.0.0-20240308111911-97652132727d/go.mod h1:goPLq6Qim+Hr7nGeDflbxPNqhErDAdhpUmFp44URbo8= 3 | -------------------------------------------------------------------------------- /wasmbadge/modules/hithere/hithere.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mnontrapping-fptoint", 15 | "-msign-ext" 16 | ], 17 | "ldflags": [ 18 | "--stack-first", 19 | "--no-demangle", 20 | "--no-entry", 21 | "--import-memory", 22 | "--initial-memory=65536", 23 | "--max-memory=65536", 24 | "-zstack-size=4096" 25 | ], 26 | "extra-files": [ 27 | "src/runtime/asm_tinygowasm.S" 28 | ], 29 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 30 | } 31 | -------------------------------------------------------------------------------- /wasmbadge/modules/hithere/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/hybridgroup/mechanoid/convert" 7 | ) 8 | 9 | const msg = "Hello, WebAssembly!" 10 | 11 | var ( 12 | counter int 13 | buf [64]byte 14 | ) 15 | 16 | //go:export start 17 | func start() { 18 | //copy(buf[:], msg) 19 | 20 | badge_set_text1(convert.StringToWasmPtr(msg)) 21 | } 22 | 23 | //go:export update 24 | func update() { 25 | m := "Count: " 26 | start, end := 0, len(m) 27 | copy(buf[start:end], m) 28 | 29 | counter++ 30 | s := convert.IntToString(counter) 31 | start = end 32 | end = end + len(s) 33 | 34 | copy(buf[start:end], s) 35 | 36 | badge_set_text3(convert.BytesToWasmPtr(buf[0:end])) 37 | } 38 | 39 | func main() {} 40 | -------------------------------------------------------------------------------- /wasmbadge/modules/mynameis.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/wasmbadge/modules/mynameis.wasm -------------------------------------------------------------------------------- /wasmbadge/modules/mynameis/README.md: -------------------------------------------------------------------------------- 1 | # My Name Is 2 | 3 | Mechanoid WebAssembly module template. 4 | 5 | ## Building 6 | 7 | -------------------------------------------------------------------------------- /wasmbadge/modules/mynameis/badge.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport badge heading 6 | func badge_heading(ptr, sz uint32) 7 | 8 | //go:wasmimport badge set_text1 9 | func badge_set_text1(ptr, sz uint32) 10 | 11 | //go:wasmimport badge set_text2 12 | func badge_set_text2(ptr, sz uint32) 13 | 14 | //go:wasmimport badge set_text3 15 | func badge_set_text3(ptr, sz uint32) 16 | 17 | //go:wasmimport badge set_text4 18 | func badge_set_text4(ptr, sz uint32) 19 | -------------------------------------------------------------------------------- /wasmbadge/modules/mynameis/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/wasmbadge/modules/mynameis 2 | 3 | go 1.22.0 4 | 5 | require github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792 6 | -------------------------------------------------------------------------------- /wasmbadge/modules/mynameis/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792 h1:yk7R096wgJ7Ekn5GBXHahew+GwusjxFXXJ8cVDYorRQ= 2 | github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792/go.mod h1:Wwv4M6aVB7554PaAYP2tm3NNNJwJK3BnJP8Pp7RxQJo= 3 | -------------------------------------------------------------------------------- /wasmbadge/modules/mynameis/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/hybridgroup/mechanoid/convert" 7 | ) 8 | 9 | const name = "@mechanoidio" 10 | const description = "WebAssembly" 11 | const description2 = "on TinyGo" 12 | const nada = "" 13 | 14 | var ( 15 | // buf is a buffer that is used to pass messages to the greeter host instance. 16 | buf [64]byte 17 | ) 18 | 19 | //go:export start 20 | func start() { 21 | badge_set_text1(convert.StringToWasmPtr(name)) 22 | } 23 | 24 | //go:export update 25 | func update() { 26 | copy(buf[:], description) 27 | badge_set_text3(convert.BytesToWasmPtr(buf[:len(description)])) 28 | 29 | copy(buf[len(description)+1:], description2) 30 | badge_set_text4(convert.BytesToWasmPtr(buf[len(description)+1 : len(description)+len(description2)+1])) 31 | } 32 | 33 | func main() {} 34 | -------------------------------------------------------------------------------- /wasmbadge/modules/mynameis/mynameis.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mnontrapping-fptoint", 15 | "-msign-ext" 16 | ], 17 | "ldflags": [ 18 | "--stack-first", 19 | "--no-demangle", 20 | "--no-entry", 21 | "--import-memory", 22 | "--initial-memory=65536", 23 | "--max-memory=65536", 24 | "-zstack-size=4096" 25 | ], 26 | "extra-files": [ 27 | "src/runtime/asm_tinygowasm.S" 28 | ], 29 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 30 | } 31 | -------------------------------------------------------------------------------- /wasmbadge/modules/mythingis.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/wasmbadge/modules/mythingis.wasm -------------------------------------------------------------------------------- /wasmbadge/modules/mythingis/README.md: -------------------------------------------------------------------------------- 1 | # My Thing Is 2 | 3 | Mechanoid WebAssembly module template. 4 | 5 | ## Building 6 | 7 | -------------------------------------------------------------------------------- /wasmbadge/modules/mythingis/badge.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport badge heading 6 | func badge_heading(ptr, sz uint32) 7 | 8 | //go:wasmimport badge set_text1 9 | func badge_set_text1(ptr, sz uint32) 10 | 11 | //go:wasmimport badge set_text2 12 | func badge_set_text2(ptr, sz uint32) 13 | 14 | //go:wasmimport badge set_text3 15 | func badge_set_text3(ptr, sz uint32) 16 | 17 | //go:wasmimport badge set_text4 18 | func badge_set_text4(ptr, sz uint32) 19 | -------------------------------------------------------------------------------- /wasmbadge/modules/mythingis/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/wasmbadge/modules/mythingis 2 | 3 | go 1.22.0 4 | 5 | require github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792 6 | -------------------------------------------------------------------------------- /wasmbadge/modules/mythingis/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792 h1:yk7R096wgJ7Ekn5GBXHahew+GwusjxFXXJ8cVDYorRQ= 2 | github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792/go.mod h1:Wwv4M6aVB7554PaAYP2tm3NNNJwJK3BnJP8Pp7RxQJo= 3 | -------------------------------------------------------------------------------- /wasmbadge/modules/mythingis/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/hybridgroup/mechanoid/convert" 7 | ) 8 | 9 | const message = "Mechanoid" 10 | const message2 = "WASM framework" 11 | const message3 = "for embedded devices" 12 | const message4 = "mechanoid.io" 13 | 14 | //go:export start 15 | func start() { 16 | badge_set_text1(convert.StringToWasmPtr(message)) 17 | badge_set_text2(convert.StringToWasmPtr(message2)) 18 | } 19 | 20 | //go:export update 21 | func update() { 22 | badge_set_text3(convert.StringToWasmPtr(message3)) 23 | badge_set_text4(convert.StringToWasmPtr(message4)) 24 | } 25 | 26 | func main() {} 27 | -------------------------------------------------------------------------------- /wasmbadge/modules/mythingis/mythingis.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mnontrapping-fptoint", 15 | "-msign-ext" 16 | ], 17 | "ldflags": [ 18 | "--stack-first", 19 | "--no-demangle", 20 | "--no-entry", 21 | "--import-memory", 22 | "--initial-memory=65536", 23 | "--max-memory=65536", 24 | "-zstack-size=4096" 25 | ], 26 | "extra-files": [ 27 | "src/runtime/asm_tinygowasm.S" 28 | ], 29 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 30 | } 31 | -------------------------------------------------------------------------------- /wasmbadge/modules/thisbadge.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/wasmbadge/modules/thisbadge.wasm -------------------------------------------------------------------------------- /wasmbadge/modules/thisbadge/README.md: -------------------------------------------------------------------------------- 1 | # This Badge 2 | 3 | Mechanoid WebAssembly module for WASMBadge. 4 | 5 | ## Building 6 | 7 | -------------------------------------------------------------------------------- /wasmbadge/modules/thisbadge/badge.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport badge heading 6 | func badge_heading(ptr, sz uint32) 7 | 8 | //go:wasmimport badge set_text1 9 | func badge_set_text1(ptr, sz uint32) 10 | 11 | //go:wasmimport badge set_text2 12 | func badge_set_text2(ptr, sz uint32) 13 | 14 | //go:wasmimport badge set_text3 15 | func badge_set_text3(ptr, sz uint32) 16 | 17 | //go:wasmimport badge set_text4 18 | func badge_set_text4(ptr, sz uint32) 19 | -------------------------------------------------------------------------------- /wasmbadge/modules/thisbadge/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/wasmbadge/modules/thisbadge 2 | 3 | go 1.22.0 4 | 5 | require github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792 6 | -------------------------------------------------------------------------------- /wasmbadge/modules/thisbadge/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792 h1:yk7R096wgJ7Ekn5GBXHahew+GwusjxFXXJ8cVDYorRQ= 2 | github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792/go.mod h1:Wwv4M6aVB7554PaAYP2tm3NNNJwJK3BnJP8Pp7RxQJo= 3 | -------------------------------------------------------------------------------- /wasmbadge/modules/thisbadge/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/hybridgroup/mechanoid/convert" 7 | ) 8 | 9 | const msg = "This badge runs" 10 | const msg2 = "WebAssembly!" 11 | const msg3 = "Mechanoid + TinyGo" 12 | 13 | //go:export start 14 | func start() { 15 | badge_set_text1(convert.StringToWasmPtr(msg)) 16 | } 17 | 18 | //go:export update 19 | func update() { 20 | badge_set_text2(convert.StringToWasmPtr(msg2)) 21 | 22 | badge_set_text4(convert.StringToWasmPtr(msg3)) 23 | } 24 | 25 | func main() {} 26 | -------------------------------------------------------------------------------- /wasmbadge/modules/thisbadge/thisbadge.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mnontrapping-fptoint", 15 | "-msign-ext" 16 | ], 17 | "ldflags": [ 18 | "--stack-first", 19 | "--no-demangle", 20 | "--no-entry", 21 | "--import-memory", 22 | "--initial-memory=65536", 23 | "--max-memory=65536", 24 | "-zstack-size=4096" 25 | ], 26 | "extra-files": [ 27 | "src/runtime/asm_tinygowasm.S" 28 | ], 29 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 30 | } 31 | -------------------------------------------------------------------------------- /wasmbadge/modules/youarehere.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/wasmbadge/modules/youarehere.wasm -------------------------------------------------------------------------------- /wasmbadge/modules/youarehere/README.md: -------------------------------------------------------------------------------- 1 | # You Are Here 2 | 3 | Mechanoid WebAssembly module for WASMBadge. 4 | 5 | ## Building 6 | 7 | -------------------------------------------------------------------------------- /wasmbadge/modules/youarehere/badge.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport badge heading 6 | func badge_heading(ptr, sz uint32) 7 | 8 | //go:wasmimport badge set_text1 9 | func badge_set_text1(ptr, sz uint32) 10 | 11 | //go:wasmimport badge set_text2 12 | func badge_set_text2(ptr, sz uint32) 13 | 14 | //go:wasmimport badge set_text3 15 | func badge_set_text3(ptr, sz uint32) 16 | 17 | //go:wasmimport badge set_text4 18 | func badge_set_text4(ptr, sz uint32) 19 | -------------------------------------------------------------------------------- /wasmbadge/modules/youarehere/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/wasmbadge/modules/youarehere 2 | 3 | go 1.22.0 4 | 5 | require github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792 6 | -------------------------------------------------------------------------------- /wasmbadge/modules/youarehere/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792 h1:yk7R096wgJ7Ekn5GBXHahew+GwusjxFXXJ8cVDYorRQ= 2 | github.com/hybridgroup/mechanoid v0.0.0-20240301084957-59def5e03792/go.mod h1:Wwv4M6aVB7554PaAYP2tm3NNNJwJK3BnJP8Pp7RxQJo= 3 | -------------------------------------------------------------------------------- /wasmbadge/modules/youarehere/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/hybridgroup/mechanoid/convert" 7 | ) 8 | 9 | const msg = "You are at" 10 | const name = "Wasm I/O" 11 | 12 | //go:export start 13 | func start() { 14 | badge_set_text2(convert.StringToWasmPtr(msg)) 15 | } 16 | 17 | //go:export update 18 | func update() { 19 | badge_set_text4(convert.StringToWasmPtr(name)) 20 | } 21 | 22 | func main() {} 23 | -------------------------------------------------------------------------------- /wasmbadge/modules/youarehere/youarehere.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mnontrapping-fptoint", 15 | "-msign-ext" 16 | ], 17 | "ldflags": [ 18 | "--stack-first", 19 | "--no-demangle", 20 | "--no-entry", 21 | "--import-memory", 22 | "--initial-memory=65536", 23 | "--max-memory=65536", 24 | "-zstack-size=4096" 25 | ], 26 | "extra-files": [ 27 | "src/runtime/asm_tinygowasm.S" 28 | ], 29 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 30 | } 31 | -------------------------------------------------------------------------------- /wasmbadge/runner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "time" 6 | 7 | "github.com/aykevl/board" 8 | "github.com/hybridgroup/mechanoid" 9 | "github.com/hybridgroup/mechanoid-examples/wasmbadge/devices/badge" 10 | "github.com/hybridgroup/mechanoid-examples/wasmbadge/devices/display" 11 | "github.com/hybridgroup/mechanoid/engine" 12 | "tinygo.org/x/drivers/pixel" 13 | ) 14 | 15 | func runWASM[T pixel.Color](module string, d *display.Device[T], b *badge.Badge[T]) error { 16 | println("Running WASM module", module) 17 | 18 | mechanoid.DebugMemory("start runWASM") 19 | runtime.GC() 20 | mechanoid.DebugMemory("start runWASM after GC") 21 | 22 | f, err := modules.Open("modules/" + module) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | if err := eng.Interpreter.Load(f.(engine.Reader)); err != nil { 28 | println(err.Error()) 29 | return err 30 | } 31 | 32 | f.Close() 33 | 34 | println("Running module...") 35 | ins, err := eng.Interpreter.Run() 36 | if err != nil { 37 | println(err.Error()) 38 | return err 39 | } 40 | 41 | // clear badge text 42 | b.Clear() 43 | b.Heading(module) 44 | 45 | if _, err := ins.Call("start"); err != nil { 46 | println(err.Error()) 47 | } 48 | 49 | for { 50 | if _, err := ins.Call("update"); err != nil { 51 | println(err.Error()) 52 | } 53 | 54 | board.Buttons.ReadInput() 55 | event := board.Buttons.NextEvent() 56 | if !event.Pressed() { 57 | continue 58 | } 59 | switch event.Key() { 60 | case board.KeySelect, board.KeyEscape, board.KeyB: 61 | return nil 62 | } 63 | 64 | d.Screen.Update() 65 | time.Sleep(time.Second / 30) 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func runWASMRotation[T pixel.Color](d *display.Device[T], b *badge.Badge[T]) error { 72 | mods, err := modules.ReadDir("modules") 73 | if err != nil { 74 | return err 75 | } 76 | 77 | i := len(mods) 78 | for { 79 | i++ 80 | if i >= len(mods) { 81 | i = 0 82 | } 83 | 84 | mod := mods[i] 85 | 86 | if mod.IsDir() { 87 | continue 88 | } 89 | 90 | println("Running WASM module", mod.Name()) 91 | 92 | mechanoid.DebugMemory("start runWASM") 93 | runtime.GC() 94 | mechanoid.DebugMemory("start runWASM after GC") 95 | 96 | f, err := modules.Open("modules/" + mod.Name()) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | if err := eng.Interpreter.Load(f.(engine.Reader)); err != nil { 102 | println(err.Error()) 103 | return err 104 | } 105 | 106 | f.Close() 107 | 108 | println("Running module", mod.Name(), "...") 109 | ins, err := eng.Interpreter.Run() 110 | if err != nil { 111 | println(err.Error()) 112 | return err 113 | } 114 | 115 | // clear badge text 116 | b.Clear() 117 | b.Heading(mod.Name()) 118 | 119 | if _, err := ins.Call("start"); err != nil { 120 | println(err.Error()) 121 | } 122 | 123 | start := time.Now() 124 | for { 125 | if time.Since(start) > 10*time.Second { 126 | break 127 | } 128 | 129 | if _, err := ins.Call("update"); err != nil { 130 | println(err.Error()) 131 | } 132 | 133 | board.Buttons.ReadInput() 134 | event := board.Buttons.NextEvent() 135 | if !event.Pressed() { 136 | continue 137 | } 138 | switch event.Key() { 139 | case board.KeySelect, board.KeyEscape, board.KeyB: 140 | // return home 141 | return nil 142 | case board.KeyA: 143 | // skip to next 144 | break 145 | } 146 | 147 | d.Screen.Update() 148 | time.Sleep(time.Second / 10) 149 | } 150 | 151 | ins = nil 152 | eng.Interpreter.Halt() 153 | } 154 | 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /wasmdrone/README.md: -------------------------------------------------------------------------------- 1 | # WASMDrone 2 | 3 | ![WASMDrone](../images/wasmdrone-pybadge-tello.jpg) 4 | 5 | This application lets you write a WebAssembly program that runs on a hardware device with connected wireless to fly a DJI Tello drone. 6 | 7 | You need a PyBadge with an AirLift WiFi or similar device to run this example. 8 | 9 | ## How it works 10 | 11 | The application has 2 devices: the Display device for showing status, and the Drone device for communicating to the connected DJI Tello drone. 12 | 13 | ```mermaid 14 | flowchart LR 15 | subgraph Microcontroller 16 | subgraph Application 17 | end 18 | subgraph Devices 19 | subgraph Drone 20 | end 21 | subgraph Display 22 | end 23 | end 24 | subgraph Modules 25 | subgraph drone.wasm 26 | end 27 | end 28 | Application--Buttons-->drone.wasm 29 | drone.wasm--Commands-->Drone 30 | drone.wasm-->Display 31 | end 32 | subgraph Tello 33 | end 34 | Drone--WiFi-->Tello 35 | ``` 36 | 37 | ## How to run 38 | 39 | ### PyBadge 40 | 41 | ``` 42 | $ mecha flash -i wasman -m -params main.ssid=TELLO-XXXXXX pybadge 43 | Building module drone 44 | Done. 45 | code data bss | flash ram 46 | 298 24 4096 | 322 4120 47 | Application built. Now flashing... 48 | code data bss | flash ram 49 | 164068 3960 6980 | 168028 10940 50 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 51 | Mechanoid engine starting... 52 | Initializing engine using interpreter wasman 53 | Registering host modules... 54 | 55 | Tinygo ESP32 Wifi network device driver (WiFiNINA) 56 | 57 | Driver version : 0.27.0 58 | ESP32 firmware version : 1.7.4 59 | MAC address : a0:a3:b3:47:e5:dc 60 | 61 | Connecting to Wifi SSID 'TELLO-XXXXXX'...FAILED (timed out) 62 | Connecting to Wifi SSID 'TELLO-XXXXXX'...CONNECTED 63 | 64 | DHCP-assigned IP : 192.168.10.2 65 | DHCP-assigned subnet : 255.255.255.0 66 | DHCP-assigned gateway : 192.168.10.1 67 | 68 | Registering host modules... 69 | Loading and running WASM code... 70 | Starting drone... 71 | ... 72 | ``` 73 | 74 | Now you can fly your drone around using your badge, thanks to the WASM code! 75 | -------------------------------------------------------------------------------- /wasmdrone/devices/display/display.go: -------------------------------------------------------------------------------- 1 | package display 2 | 3 | import ( 4 | "github.com/aykevl/board" 5 | "github.com/aykevl/tinygl" 6 | "github.com/aykevl/tinygl/style" 7 | "github.com/aykevl/tinygl/style/basic" 8 | "github.com/orsinium-labs/wypes" 9 | "tinygo.org/x/drivers/pixel" 10 | ) 11 | 12 | var pongCount int 13 | 14 | type Device[T pixel.Color] struct { 15 | Display board.Displayer[T] 16 | Screen *tinygl.Screen[T] 17 | Theme *basic.Basic[T] 18 | VBox *tinygl.VBox[T] 19 | Header *tinygl.Text[T] 20 | Message1Text *tinygl.Text[T] 21 | Message2Text *tinygl.Text[T] 22 | } 23 | 24 | // NewDevice creates a new display device. 25 | func NewDevice[T pixel.Color](display board.Displayer[T]) *Device[T] { 26 | return &Device[T]{ 27 | Display: display, 28 | } 29 | } 30 | 31 | func (d *Device[T]) Modules() wypes.Modules { 32 | return wypes.Modules{ 33 | "display": wypes.Module{ 34 | "heading": wypes.H1(d.heading), 35 | "message1": wypes.H1(d.message1), 36 | "message2": wypes.H1(d.message2), 37 | }, 38 | } 39 | } 40 | 41 | func (d *Device[T]) Init() error { 42 | width, height := d.Display.Size() 43 | scalePercent := board.Display.PPI() * 100 / 120 44 | 45 | // Initialize the screen. 46 | buf := pixel.NewImage[T](int(width), int(height)/4) 47 | d.Screen = tinygl.NewScreen[T](d.Display, buf, board.Display.PPI()) 48 | d.Theme = basic.NewTheme(style.NewScale(scalePercent), d.Screen) 49 | d.Header = d.Theme.NewText("Mechanoid") 50 | d.Message1Text = d.Theme.NewText("") 51 | d.Message2Text = d.Theme.NewText("") 52 | d.VBox = d.Theme.NewVBox(d.Header, d.Message1Text, d.Message2Text) 53 | 54 | d.Screen.SetChild(d.VBox) 55 | d.Screen.Update() 56 | board.Display.SetBrightness(board.Display.MaxBrightness()) 57 | 58 | return nil 59 | } 60 | 61 | func (d *Device[T]) Heading(msg string) { 62 | d.Header.SetText(msg) 63 | d.Screen.Update() 64 | } 65 | 66 | func (d *Device[T]) Message1(msg string) { 67 | d.Message1Text.SetText(msg) 68 | d.Screen.Update() 69 | } 70 | 71 | func (d *Device[T]) Message2(msg string) { 72 | d.Message2Text.SetText(msg) 73 | d.Screen.Update() 74 | } 75 | 76 | func (d *Device[T]) heading(msg wypes.String) wypes.Void { 77 | d.Heading(msg.Unwrap()) 78 | return wypes.Void{} 79 | } 80 | 81 | func (d *Device[T]) message1(msg wypes.String) wypes.Void { 82 | d.Message1(msg.Unwrap()) 83 | return wypes.Void{} 84 | } 85 | 86 | func (d *Device[T]) message2(msg wypes.String) wypes.Void { 87 | d.Message2(msg.Unwrap()) 88 | return wypes.Void{} 89 | } 90 | -------------------------------------------------------------------------------- /wasmdrone/devices/drone/drone.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/hybridgroup/mechanoid" 8 | tello "github.com/hybridgroup/tinygo-tello" 9 | "github.com/orsinium-labs/wypes" 10 | "tinygo.org/x/drivers/netlink" 11 | "tinygo.org/x/drivers/netlink/probe" 12 | ) 13 | 14 | const ( 15 | DirectionNone = iota 16 | DirectionForward 17 | DirectionBackward 18 | DirectionLeft 19 | DirectionRight 20 | DirectionUp 21 | DirectionDown 22 | DirectionTurnLeft 23 | DirectionTurnRight 24 | ) 25 | 26 | var errNoSSID = errors.New("no SSID provided") 27 | 28 | type Device struct { 29 | drone *tello.Tello 30 | ssid, pass string 31 | connected bool 32 | Direction int 33 | Speed int 34 | } 35 | 36 | func NewDevice(ssid, pass string) *Device { 37 | return &Device{ 38 | ssid: ssid, 39 | pass: pass, 40 | } 41 | } 42 | 43 | func (d *Device) Modules() wypes.Modules { 44 | return wypes.Modules{ 45 | "drone": wypes.Module{ 46 | "control": wypes.H2(d.control), 47 | "takeoff": wypes.H1(d.takeoff), 48 | "land": wypes.H1(d.land), 49 | "flip": wypes.H1(d.flip), 50 | }, 51 | } 52 | } 53 | 54 | func (d *Device) control(direction, speed wypes.UInt32) wypes.Void { 55 | d.Direction = int(direction.Unwrap()) 56 | d.Speed = int(speed.Unwrap()) 57 | 58 | return wypes.Void{} 59 | } 60 | 61 | func (d *Device) takeoff(kind wypes.UInt32) wypes.Void { 62 | // TODO: simple debounce 63 | switch kind.Unwrap() { 64 | case 0: 65 | d.TakeOff() 66 | case 1: 67 | d.ThrowTakeOff() 68 | } 69 | return wypes.Void{} 70 | } 71 | 72 | func (d *Device) land(kind wypes.UInt32) wypes.Void { 73 | // TODO: simple debounce 74 | switch kind.Unwrap() { 75 | case 0: 76 | d.Land() 77 | case 1: 78 | d.PalmLand() 79 | } 80 | return wypes.Void{} 81 | } 82 | 83 | func (d *Device) flip(kind wypes.UInt32) wypes.Void { 84 | // TODO: simple debounce 85 | d.Flip(int(kind.Unwrap())) 86 | return wypes.Void{} 87 | } 88 | 89 | func (d *Device) Init() error { 90 | if d.ssid == "" { 91 | return errNoSSID 92 | } 93 | 94 | mechanoid.Debug("connecting to drone") 95 | link, _ := probe.Probe() 96 | err := link.NetConnect(&netlink.ConnectParams{ 97 | Ssid: d.ssid, 98 | Passphrase: d.pass, 99 | }) 100 | 101 | if err != nil { 102 | return err 103 | } 104 | 105 | d.drone = tello.New("8888") 106 | 107 | return nil 108 | } 109 | 110 | func (d *Device) Start() error { 111 | if err := d.drone.Start(); err != nil { 112 | return err 113 | } 114 | 115 | d.connected = true 116 | return nil 117 | } 118 | 119 | func (d *Device) Control() { 120 | for { 121 | if !d.connected { 122 | time.Sleep(100 * time.Millisecond) 123 | continue 124 | } 125 | 126 | switch d.Direction { 127 | case DirectionForward: 128 | d.drone.Forward(d.Speed) 129 | case DirectionBackward: 130 | d.drone.Backward(d.Speed) 131 | default: 132 | d.drone.Forward(0) 133 | } 134 | 135 | switch d.Direction { 136 | case DirectionLeft: 137 | d.drone.Left(d.Speed) 138 | case DirectionRight: 139 | d.drone.Right(d.Speed) 140 | default: 141 | d.drone.Right(0) 142 | } 143 | 144 | switch d.Direction { 145 | case DirectionUp: 146 | d.drone.Up(d.Speed) 147 | case DirectionDown: 148 | d.drone.Down(d.Speed) 149 | default: 150 | d.drone.Up(0) 151 | } 152 | 153 | switch d.Direction { 154 | case DirectionTurnLeft: 155 | d.drone.CounterClockwise(d.Speed) 156 | case DirectionTurnRight: 157 | d.drone.Clockwise(d.Speed) 158 | default: 159 | d.drone.Clockwise(0) 160 | } 161 | 162 | time.Sleep(100 * time.Millisecond) 163 | } 164 | } 165 | 166 | func (d *Device) TakeOff() error { 167 | return d.drone.TakeOff() 168 | } 169 | 170 | func (d *Device) ThrowTakeOff() error { 171 | return d.drone.ThrowTakeOff() 172 | } 173 | 174 | func (d *Device) Land() error { 175 | return d.drone.Land() 176 | } 177 | 178 | func (d *Device) PalmLand() error { 179 | return d.drone.PalmLand() 180 | } 181 | 182 | func (d *Device) Flip(kind int) error { 183 | return d.drone.Flip(tello.FlipType(kind)) 184 | } 185 | -------------------------------------------------------------------------------- /wasmdrone/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/wasmdrone 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/aykevl/board v0.0.0-20240106144210-80ca76f77def 7 | github.com/aykevl/tinygl v0.0.0-20240131130748-3033a2fd9182 8 | github.com/hybridgroup/mechanoid v0.2.0 9 | github.com/hybridgroup/tinygo-tello v0.0.0-20240310133819-eb8919c64e88 10 | github.com/orsinium-labs/wypes v0.1.4 11 | tinygo.org/x/drivers v0.27.0 12 | ) 13 | 14 | require ( 15 | fyne.io/fyne/v2 v2.3.4 // indirect 16 | fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/fredbi/uri v0.1.0 // indirect 19 | github.com/fsnotify/fsnotify v1.5.4 // indirect 20 | github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect 21 | github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect 22 | github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect 23 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect 24 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect 25 | github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 // indirect 26 | github.com/godbus/dbus/v5 v5.1.0 // indirect 27 | github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect 28 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 29 | github.com/gopherjs/gopherjs v1.17.2 // indirect 30 | github.com/hybridgroup/wasman v0.0.0-20240304140329-ce1ea6b61834 // indirect 31 | github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect 32 | github.com/pmezard/go-difflib v1.0.0 // indirect 33 | github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 // indirect 34 | github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect 35 | github.com/stretchr/testify v1.8.0 // indirect 36 | github.com/tetratelabs/wazero v1.7.1-0.20240401054209-4d6585d7da56 // indirect 37 | github.com/tevino/abool v1.2.0 // indirect 38 | github.com/yuin/goldmark v1.4.13 // indirect 39 | golang.org/x/image v0.3.0 // indirect 40 | golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect 41 | golang.org/x/net v0.7.0 // indirect 42 | golang.org/x/sys v0.5.0 // indirect 43 | golang.org/x/text v0.7.0 // indirect 44 | gopkg.in/yaml.v3 v3.0.1 // indirect 45 | honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect 46 | ) 47 | -------------------------------------------------------------------------------- /wasmdrone/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "time" 7 | 8 | "github.com/aykevl/board" 9 | "github.com/hybridgroup/mechanoid-examples/wasmdrone/devices/display" 10 | "github.com/hybridgroup/mechanoid-examples/wasmdrone/devices/drone" 11 | "github.com/hybridgroup/mechanoid/engine" 12 | "github.com/hybridgroup/mechanoid/interp" 13 | "tinygo.org/x/drivers/pixel" 14 | ) 15 | 16 | //go:embed modules/drone.wasm 17 | var wasmCode []byte 18 | 19 | var ( 20 | eng *engine.Engine 21 | ) 22 | 23 | var ssid, pass string 24 | 25 | // main func just calls a run() func so we can infer the display type. 26 | func main() { 27 | run(board.Display.Configure()) 28 | } 29 | 30 | // run func is the main entry point for the program. 31 | func run[T pixel.Color](disp board.Displayer[T]) { 32 | time.Sleep(2 * time.Second) 33 | 34 | println("Mechanoid engine starting...") 35 | eng := engine.NewEngine() 36 | eng.UseInterpreter(interp.NewInterpreter()) 37 | dsp := display.NewDevice(disp) 38 | eng.AddDevice(dsp) 39 | 40 | tello := drone.NewDevice(ssid, pass) 41 | eng.AddDevice(tello) 42 | 43 | println("Initializing engine using interpreter", eng.Interpreter.Name()) 44 | if err := eng.Init(); err != nil { 45 | println(err.Error()) 46 | return 47 | } 48 | 49 | board.Buttons.Configure() 50 | 51 | dsp.Heading("WASM Drone") 52 | dsp.Message1Text.SetText("Loading code...") 53 | 54 | println("Loading and running WASM code...") 55 | ins, err := eng.LoadAndRun(bytes.NewReader(wasmCode)) 56 | if err != nil { 57 | println(err.Error()) 58 | return 59 | } 60 | 61 | dsp.Message1Text.SetText("Starting drone...") 62 | println("Starting drone...") 63 | 64 | if err := tello.Start(); err != nil { 65 | println(err.Error()) 66 | return 67 | } 68 | go tello.Control() 69 | 70 | dsp.Message1Text.SetText("Ready") 71 | 72 | shifted := int32(0) 73 | for { 74 | board.Buttons.ReadInput() 75 | event := board.Buttons.NextEvent() 76 | // handled "shifting" using A 77 | if event.Key() == board.KeyA && event.Pressed() { 78 | shifted = 1 79 | continue 80 | } else if event.Key() == board.KeyA && !event.Pressed() { 81 | shifted = 0 82 | continue 83 | } 84 | 85 | if !event.Pressed() { 86 | // key released, so set direction/speed to none 87 | tello.Direction = drone.DirectionNone 88 | tello.Speed = 0 89 | 90 | continue 91 | } 92 | switch event.Key() { 93 | // case board.KeyA: 94 | // // "shifter" 95 | // if _, err := ins.Call("button_a"); err != nil { 96 | // println(err.Error()) 97 | // } 98 | case board.KeyStart: 99 | // takeoff 100 | if _, err := ins.Call("button_start", shifted); err != nil { 101 | println(err.Error()) 102 | } 103 | case board.KeyB: 104 | // lander 105 | if _, err := ins.Call("button_b", shifted); err != nil { 106 | println(err.Error()) 107 | } 108 | case board.KeySelect: 109 | // flip 110 | if _, err := ins.Call("button_select", shifted); err != nil { 111 | println(err.Error()) 112 | } 113 | case board.KeyUp: 114 | if _, err := ins.Call("button_up", shifted); err != nil { 115 | println(err.Error()) 116 | } 117 | case board.KeyDown: 118 | if _, err := ins.Call("button_down", shifted); err != nil { 119 | println(err.Error()) 120 | } 121 | case board.KeyLeft: 122 | if _, err := ins.Call("button_left", shifted); err != nil { 123 | println(err.Error()) 124 | } 125 | case board.KeyRight: 126 | if _, err := ins.Call("button_right", shifted); err != nil { 127 | println(err.Error()) 128 | } 129 | } 130 | 131 | time.Sleep(time.Second / 10) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /wasmdrone/modules/drone.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/mechanoid-examples/4a5a4843311a27bb4fbf1ac1b22957a32b2f9654/wasmdrone/modules/drone.wasm -------------------------------------------------------------------------------- /wasmdrone/modules/drone/README.md: -------------------------------------------------------------------------------- 1 | # Hello 2 | 3 | Mechanoid WebAssembly module template. 4 | 5 | ## Building 6 | 7 | -------------------------------------------------------------------------------- /wasmdrone/modules/drone/display.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | //go:wasmimport display heading 6 | func heading(ptr, sz uint32) 7 | 8 | //go:wasmimport display message1 9 | func message1(ptr, sz uint32) 10 | 11 | //go:wasmimport display message2 12 | func message2(ptr, sz uint32) 13 | -------------------------------------------------------------------------------- /wasmdrone/modules/drone/drone.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | DirectionNone = iota 5 | DirectionForward 6 | DirectionBackward 7 | DirectionLeft 8 | DirectionRight 9 | DirectionUp 10 | DirectionDown 11 | DirectionTurnLeft 12 | DirectionTurnRight 13 | ) 14 | 15 | const ( 16 | TakeoffNormal = iota 17 | TakeoffThrow 18 | ) 19 | 20 | const ( 21 | LandingNormal = iota 22 | LandingHand 23 | ) 24 | 25 | const ( 26 | FlipFront = iota 27 | FlipLeft 28 | FlipBack 29 | FlipRight 30 | FlipForwardLeft 31 | FlipBackLeft 32 | FlipBackRight 33 | FlipForwardRight 34 | ) 35 | 36 | const defaultSpeed = 30 37 | 38 | //go:wasmimport drone control 39 | func droneControl(direction, speed uint32) 40 | 41 | //go:wasmimport drone takeoff 42 | func droneTakeoff(kind uint32) 43 | 44 | //go:wasmimport drone land 45 | func droneLand(kind uint32) 46 | 47 | //go:wasmimport drone flip 48 | func droneFlip(kind uint32) 49 | -------------------------------------------------------------------------------- /wasmdrone/modules/drone/drone.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "wasm32-unknown-unknown", 3 | "cpu": "generic", 4 | "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,+bulk-memory", 5 | "build-tags": ["tinygo.wasm", "wasm_unknown"], 6 | "goos": "linux", 7 | "goarch": "arm", 8 | "linker": "wasm-ld", 9 | "gc": "leaking", 10 | "libc": "wasi-libc", 11 | "rtlib": "compiler-rt", 12 | "scheduler": "none", 13 | "cflags": [ 14 | "-mnontrapping-fptoint", 15 | "-msign-ext" 16 | ], 17 | "ldflags": [ 18 | "--stack-first", 19 | "--no-demangle", 20 | "--no-entry", 21 | "--import-memory", 22 | "--initial-memory=65536", 23 | "--max-memory=65536", 24 | "-zstack-size=4096" 25 | ], 26 | "extra-files": [ 27 | "src/runtime/asm_tinygowasm.S" 28 | ], 29 | "emulator": "wasmtime --dir={tmpDir}::/tmp {}" 30 | } 31 | -------------------------------------------------------------------------------- /wasmdrone/modules/drone/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hybridgroup/mechanoid-examples/wasmdrone/modules/drone 2 | 3 | go 1.22.0 4 | 5 | require github.com/hybridgroup/mechanoid v0.0.0-20240310110742-7303f6e2a7b5 6 | -------------------------------------------------------------------------------- /wasmdrone/modules/drone/go.sum: -------------------------------------------------------------------------------- 1 | github.com/hybridgroup/mechanoid v0.0.0-20240310110742-7303f6e2a7b5 h1:iqz0NQFdB5pOOMcNZdtji8Q6vHGIRgqFnhhpuZUpOuA= 2 | github.com/hybridgroup/mechanoid v0.0.0-20240310110742-7303f6e2a7b5/go.mod h1:goPLq6Qim+Hr7nGeDflbxPNqhErDAdhpUmFp44URbo8= 3 | -------------------------------------------------------------------------------- /wasmdrone/modules/drone/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/hybridgroup/mechanoid/convert" 7 | ) 8 | 9 | var ( 10 | // buf is a buffer that is used to pass messages to the host instance. 11 | buf [64]byte 12 | ) 13 | 14 | //go:export button_select 15 | func buttonSelect(shift int32) { 16 | msg := "Flip!" 17 | copy(buf[:], msg) 18 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 19 | 20 | message1(ptr, sz) 21 | 22 | kind := FlipFront 23 | if shift == 1 { 24 | kind = FlipBack 25 | } 26 | droneFlip(uint32(kind)) 27 | } 28 | 29 | //go:export button_start 30 | func buttonStart(shift int32) { 31 | msg := "Takeoff" 32 | copy(buf[:], msg) 33 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 34 | 35 | message1(ptr, sz) 36 | 37 | kind := TakeoffNormal 38 | if shift == 1 { 39 | kind = TakeoffThrow 40 | } 41 | droneTakeoff(uint32(kind)) 42 | } 43 | 44 | //go:export button_b 45 | func buttonB(shift int32) { 46 | msg := "Landing" 47 | copy(buf[:], msg) 48 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 49 | 50 | message1(ptr, sz) 51 | 52 | kind := LandingNormal 53 | if shift == 1 { 54 | kind = LandingHand 55 | } 56 | droneLand(uint32(kind)) 57 | } 58 | 59 | //go:export button_up 60 | func buttonUp(shift int32) { 61 | msg := "UP" 62 | copy(buf[:], msg) 63 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 64 | message1(ptr, sz) 65 | 66 | direction := DirectionForward 67 | if shift == 1 { 68 | direction = DirectionUp 69 | } 70 | 71 | droneControl(uint32(direction), defaultSpeed) 72 | } 73 | 74 | //go:export button_down 75 | func buttonDown(shift int32) { 76 | msg := "DOWN" 77 | copy(buf[:], msg) 78 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 79 | 80 | message1(ptr, sz) 81 | 82 | direction := DirectionBackward 83 | if shift == 1 { 84 | direction = DirectionDown 85 | } 86 | 87 | droneControl(uint32(direction), defaultSpeed) 88 | } 89 | 90 | //go:export button_left 91 | func buttonLeft(shift int32) { 92 | msg := "LEFT" 93 | copy(buf[:], msg) 94 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 95 | 96 | message1(ptr, sz) 97 | 98 | direction := DirectionLeft 99 | if shift == 1 { 100 | direction = DirectionTurnLeft 101 | } 102 | 103 | droneControl(uint32(direction), defaultSpeed) 104 | } 105 | 106 | //go:export button_right 107 | func buttonRight(shift int32) { 108 | msg := "RIGHT" 109 | copy(buf[:], msg) 110 | ptr, sz := convert.BytesToWasmPtr(buf[:len(msg)]) 111 | 112 | message1(ptr, sz) 113 | 114 | direction := DirectionRight 115 | if shift == 1 { 116 | direction = DirectionTurnRight 117 | } 118 | 119 | droneControl(uint32(direction), defaultSpeed) 120 | } 121 | 122 | func main() {} 123 | --------------------------------------------------------------------------------