├── .gitignore ├── .travis.yml ├── README.md ├── assemblyscript ├── app.ts ├── arduino.ts ├── package-lock.json ├── package.json ├── serial.ts ├── tsconfig.json ├── wifi.ts └── yarn.lock ├── include └── README ├── lib └── README ├── platformio.ini ├── rust ├── app.rs ├── arduino_api.rs └── build.sh ├── src ├── m3_arduino_api.cpp ├── m3_arduino_api.h └── main.cpp ├── test └── README ├── tinygo ├── app.go ├── arduino │ └── arduino.go ├── build.sh ├── go.mod ├── serial │ └── serial.go └── wifi │ └── wifi.go └── web ├── index.html ├── index.js ├── package-lock.json ├── package.json └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | node_modules 7 | *.wasm 8 | *.wat 9 | app.wasm.h 10 | .vscode/ 11 | web/dist 12 | web/.cache -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Running WASM on Arduino 2 | 3 | Testing WebAssembly on IoT devices. 4 | 5 | This is a Work in Progress - [Demo video](https://twitter.com/alvaroviebrantz/status/1221618910803513344?s=20) 6 | 7 | ### OTA Update WASM file 8 | 9 | The file is being saved on SPIFFS and the Arduino is loading from it to run on WASM3 VM. So we can update the WASM code without changing the host code. 10 | 11 | Steps to update WASM code: 12 | 13 | * `cd` into `assemblyscript` folder and run `npm run build`. 14 | * This will compile the AS code to Wasm and also copy the file to the `data` folder 15 | 16 | #### To update locally via cable 17 | * Run the command `pio run --target uploadfs` 18 | 19 | #### To update via WiFi 20 | * Your device needs to sucessfully connect to the network. Then follow the steps: 21 | * First search for the MDNS service using `dns-sd -B _ota .`. Example output: 22 | ``` 23 | Browsing for _ota._tcp 24 | DATE: ---Thu 23 Jan 2020--- 25 | 0:04:57.636 ...STARTING... 26 | Timestamp A/R Flags if Domain Service Type Instance Name 27 | 0:07:10.852 Add 2 8 local. _ota._tcp. esp-38A4AE30 28 | ``` 29 | * Them send an Wasm file on the /upload endpoint on the device: 30 | ``` 31 | curl -X POST -F "app.wasm=@./app.wasm" http://esp-38A4AE30.local/upload 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /assemblyscript/app.ts: -------------------------------------------------------------------------------- 1 | import * as WiFi from "./wifi"; 2 | import { 3 | HIGH, 4 | LOW, 5 | OUTPUT, 6 | millis, 7 | delay, 8 | pinMode, 9 | digitalWrite, 10 | getPinLED, 11 | getChipID, 12 | } from "./arduino"; 13 | import * as Serial from "./serial"; 14 | 15 | let ledPin: u32 = -1; 16 | 17 | const ssid = "YOUR_SSID"; 18 | const password = "YOUR_PASSWORD"; 19 | 20 | function connect(): void { 21 | if (WiFi.status() === WiFi.WL_CONNECTED) { 22 | return; 23 | } 24 | 25 | WiFi.connect(ssid, password); 26 | Serial.println("Connecting"); 27 | let attempts = 0; 28 | while (WiFi.status() != WiFi.WL_CONNECTED) { 29 | delay(500); 30 | Serial.print("."); 31 | attempts++; 32 | if (attempts >= 10) { 33 | Serial.println("Failed to connect!"); 34 | return; 35 | } 36 | } 37 | Serial.println("Connected!"); 38 | Serial.println(WiFi.localIp()); 39 | } 40 | 41 | const blinkInterval: u32 = 1000; 42 | let lastMillis: u32 = 0; 43 | let ledState: bool = false; 44 | let deviceId: string = ""; 45 | 46 | function setup(): void { 47 | ledPin = getPinLED(); 48 | pinMode(ledPin, OUTPUT); 49 | digitalWrite(ledPin, ledState ? HIGH : LOW); 50 | lastMillis = millis(); 51 | Serial.println("Hello from AssemblyScript 😊"); 52 | deviceId = getChipID(); 53 | } 54 | 55 | function run(): void { 56 | connect(); 57 | const currentMillis: u32 = millis(); 58 | if (currentMillis - lastMillis >= blinkInterval) { 59 | const connected = WiFi.status() === WiFi.WL_CONNECTED; 60 | const localIp = WiFi.localIp(); 61 | Serial.println( 62 | "[" + 63 | currentMillis.toString() + 64 | "] [" + 65 | deviceId + 66 | "] [connected : " + 67 | connected.toString() + 68 | "] [" + 69 | localIp + 70 | "] AssemblyScript 😊" 71 | ); 72 | ledState = !ledState; 73 | digitalWrite(ledPin, ledState ? HIGH : LOW); 74 | lastMillis = millis(); 75 | } 76 | delay(10); 77 | } 78 | 79 | /* 80 | * Entry point 81 | */ 82 | export function _start(): void { 83 | setup(); 84 | while (1) run(); 85 | } 86 | -------------------------------------------------------------------------------- /assemblyscript/arduino.ts: -------------------------------------------------------------------------------- 1 | @external("arduino", "millis") 2 | export declare function millis(): u32; 3 | 4 | @external("arduino", "delay") 5 | export declare function delay(ms: u32): void; 6 | 7 | @external("arduino", "pinMode") 8 | export declare function pinMode(pin: u32, mode: u32): void; 9 | 10 | @external("arduino", "digitalWrite") 11 | export declare function digitalWrite(pin: u32, value: u32): void; 12 | 13 | @external("arduino", "getPinLED") 14 | export declare function getPinLED(): u32; 15 | 16 | @external("arduino", "getChipID") 17 | declare function _getChipID(ptr: usize): void; 18 | 19 | export function getChipID(): string { 20 | const ptr = memory.data(16); 21 | _getChipID(ptr); 22 | return String.UTF8.decodeUnsafe(ptr, 16, true); 23 | } 24 | 25 | export const LOW: u32 = 0; 26 | export const HIGH: u32 = 1; 27 | 28 | export const INPUT: u32 = 0x0; 29 | export const OUTPUT: u32 = 0x1; 30 | export const INPUT_PULLUP: u32 = 0x2; 31 | -------------------------------------------------------------------------------- /assemblyscript/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-arduino-wifi-app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "assemblyscript": { 8 | "version": "0.13.6", 9 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.13.6.tgz", 10 | "integrity": "sha512-08dr8yrzwXNbeQQYP/5fy0DO4FC/AWpRYLGfzMQBTNbC37y1d1z8GFR9Lm7zAPZiT4lpqnikpgLaTP5S6AWg4Q==", 11 | "dev": true, 12 | "requires": { 13 | "binaryen": "93.0.0-nightly.20200609", 14 | "long": "^4.0.0" 15 | } 16 | }, 17 | "binaryen": { 18 | "version": "93.0.0-nightly.20200609", 19 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-93.0.0-nightly.20200609.tgz", 20 | "integrity": "sha512-CIaeav05u+fWRN2h1ecwIoSaOF/Mk6U85M/G6eg1nOHAXYYmOuh9TztF9Fu8krRWnl98J3W+VfDClApMV5zCtw==", 21 | "dev": true 22 | }, 23 | "long": { 24 | "version": "4.0.0", 25 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 26 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", 27 | "dev": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /assemblyscript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-arduino-wifi-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npm run asbuild:optimized && xxd -i app.wasm > app.wasm.h && cp app.wasm ../data/app.wasm", 8 | "asbuild:optimized": "npx asc app.ts -b app.wasm -t app.wat -O3z --runtime half --noAssert --use abort=" 9 | }, 10 | "devDependencies": { 11 | "assemblyscript": "^0.13.6" 12 | }, 13 | "author": "Alvaro Viebrantz", 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /assemblyscript/serial.ts: -------------------------------------------------------------------------------- 1 | 2 | @external("serial", "print") 3 | declare function _print(ptr: usize, len: i32): void; 4 | 5 | export function print(str: string): void { 6 | let out = String.UTF8.encode(str); 7 | _print(changetype(out), out.byteLength); 8 | } 9 | 10 | export function println(str: string): void { 11 | print(str + '\n') 12 | } -------------------------------------------------------------------------------- /assemblyscript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/assemblyscript/std/assembly.json", 3 | "include": [ 4 | "./**/*.ts" 5 | ] 6 | } -------------------------------------------------------------------------------- /assemblyscript/wifi.ts: -------------------------------------------------------------------------------- 1 | 2 | @external("wifi", "wifiStatus") 3 | declare function _wifiStatus(): u32; 4 | 5 | @external("wifi", "wifiConnect") 6 | declare function _wifiConnect(ssid: usize, ssid_len: i32, pass: usize, pass_len: i32): void; 7 | 8 | @external("wifi", "wifiLocalIp") 9 | declare function _wifiLocalIp(ptr: usize): void; 10 | 11 | export function connect(ssid: string, pass: string): void { 12 | let outSsid = String.UTF8.encode(ssid); 13 | let outPass = String.UTF8.encode(pass); 14 | _wifiConnect( 15 | changetype(outSsid), outSsid.byteLength, 16 | changetype(outPass), outPass.byteLength 17 | ) 18 | } 19 | 20 | export function status(): u32 { 21 | return _wifiStatus(); 22 | } 23 | 24 | export function localIp(): string { 25 | const ptr = memory.data(16); 26 | _wifiLocalIp(ptr); 27 | return String.UTF8.decodeUnsafe(ptr, 16, true); 28 | } 29 | 30 | export const WL_NO_SHIELD: u32 = 0x255; 31 | export const WL_IDLE_STATUS: u32 = 0x0; 32 | export const WL_NO_SSID_AVAIL: u32 = 0x1; 33 | export const WL_SCAN_COMPLETED: u32 = 0x2; 34 | export const WL_CONNECTED: u32 = 0x3; 35 | export const WL_CONNECT_FAILED: u32 = 0x4; 36 | export const WL_CONNECTION_LOST: u32 = 0x5; 37 | export const WL_DISCONNECTED: u32 = 0x6; 38 | -------------------------------------------------------------------------------- /assemblyscript/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | assemblyscript@^0.13.6: 6 | version "0.13.6" 7 | resolved "https://registry.yarnpkg.com/assemblyscript/-/assemblyscript-0.13.6.tgz#2654bb13e99d23e45433b8fdd58f82cff129262d" 8 | integrity sha512-08dr8yrzwXNbeQQYP/5fy0DO4FC/AWpRYLGfzMQBTNbC37y1d1z8GFR9Lm7zAPZiT4lpqnikpgLaTP5S6AWg4Q== 9 | dependencies: 10 | binaryen "93.0.0-nightly.20200609" 11 | long "^4.0.0" 12 | 13 | binaryen@93.0.0-nightly.20200609: 14 | version "93.0.0-nightly.20200609" 15 | resolved "https://registry.yarnpkg.com/binaryen/-/binaryen-93.0.0-nightly.20200609.tgz#e7c9211911c0cff94b8682e895e4de630f110cd5" 16 | integrity sha512-CIaeav05u+fWRN2h1ecwIoSaOF/Mk6U85M/G6eg1nOHAXYYmOuh9TztF9Fu8krRWnl98J3W+VfDClApMV5zCtw== 17 | 18 | long@^4.0.0: 19 | version "4.0.0" 20 | resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" 21 | integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== 22 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | [env:esp32] 2 | platform = espressif32 3 | board = esp32dev 4 | framework = arduino 5 | monitor_port=/dev/tty.SLAB_USBtoUART 6 | upload_port=/dev/tty.SLAB_USBtoUART 7 | monitor_speed = 115200 8 | 9 | src_build_flags = 10 | -Dd_m3LogOutput=false 11 | -DLED_BUILTIN=5 12 | -DESP32 13 | -O3 -flto 14 | -Wno-unused-function 15 | -Wno-unused-variable 16 | -Wno-unused-parameter 17 | -Wno-missing-field-initializers 18 | 19 | lib_deps= 20 | ESP Async WebServer 21 | wasm3/wasm3 22 | 23 | lib_extra_dirs= 24 | .pio/libdeps/esp32/wasm3 25 | -------------------------------------------------------------------------------- /rust/app.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | mod arduino_api; 4 | use arduino_api::*; 5 | 6 | const BLINK_INTERVAL: u32 = 1000; 7 | const SSID : &str = "YOUR_SSID"; 8 | const PASSWORD : &str = "YOUR_PASS"; 9 | 10 | struct App { 11 | led_pin: u32, 12 | last_millis: u32, 13 | led_state: bool, 14 | } 15 | 16 | impl App { 17 | 18 | fn new() -> Self { 19 | let led_pin = getPinLED(); 20 | let led_state = false; 21 | let last_millis = millis(); 22 | 23 | pinMode(led_pin, OUTPUT); 24 | digitalWrite(led_pin, LOW ); 25 | 26 | serialPrintln("Hello from Rust 🦀"); 27 | Self { led_pin, led_state, last_millis } 28 | } 29 | 30 | fn connect(&self) { 31 | if wifiStatus() == WL_CONNECTED { 32 | return; 33 | } 34 | 35 | wifiConnect(SSID, PASSWORD); 36 | serialPrintln("Connecting"); 37 | let mut attempts = 0; 38 | while wifiStatus() != WL_CONNECTED { 39 | delay(500); 40 | serialPrint("."); 41 | attempts = attempts + 1; 42 | if attempts >= 10 { 43 | serialPrintln("Failed to connect!"); 44 | return; 45 | } 46 | } 47 | serialPrintln("Connected!"); 48 | printWifiLocalIp(); 49 | serialPrintln(""); 50 | } 51 | 52 | fn run(&mut self) { 53 | self.connect(); 54 | let current_millis: u32 = millis(); 55 | if current_millis - self.last_millis >= BLINK_INTERVAL { 56 | let connected: bool = wifiStatus() == WL_CONNECTED; 57 | 58 | serialPrint("["); 59 | serialPrintInt(current_millis); 60 | serialPrint("][connected : "); 61 | serialPrint(if connected { "true" } else { "false" }); 62 | serialPrint("]["); 63 | printWifiLocalIp(); 64 | serialPrintln("] Rust 🦀"); 65 | 66 | self.led_state = !self.led_state; 67 | digitalWrite(self.led_pin, if self.led_state { HIGH } else { LOW }); 68 | 69 | self.last_millis = millis(); 70 | } 71 | delay(10); 72 | } 73 | } 74 | 75 | /* 76 | * Entry point 77 | */ 78 | 79 | #[no_mangle] 80 | pub extern fn _start() { 81 | let mut app = App::new(); 82 | loop { 83 | app.run(); 84 | } 85 | } 86 | 87 | #[panic_handler] 88 | fn handle_panic(_: &core::panic::PanicInfo) -> ! { 89 | loop {} 90 | } -------------------------------------------------------------------------------- /rust/arduino_api.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(non_snake_case)] 3 | 4 | #[link(wasm_import_module = "arduino")] 5 | extern { 6 | #[link_name = "millis"] fn unsafe_millis() -> u32; 7 | #[link_name = "delay"] fn unsafe_delay(ms: u32); 8 | #[link_name = "pinMode"] fn unsafe_pinMode(pin:u32, mode:u32); 9 | #[link_name = "digitalWrite"] fn unsafe_digitalWrite(pin:u32, value:u32); 10 | #[link_name = "getPinLED"] fn unsafe_getPinLED() -> u32; 11 | #[link_name = "getChipID"] fn unsafe_getChipID(buf : *mut u8); 12 | } 13 | 14 | #[link(wasm_import_module = "serial")] 15 | extern { 16 | #[link_name = "print"] fn unsafe_print(ptr: *const u8, size: usize); 17 | #[link_name = "printInt"] fn unsafe_print_int(out: u32); 18 | } 19 | 20 | #[link(wasm_import_module = "wifi")] 21 | extern { 22 | #[link_name = "wifiConnect"] fn unsafe_wifi_connect(ssid: *const u8, ssid_len : usize, password: *const u8, password_len : usize); 23 | #[link_name = "wifiStatus"] fn unsafe_wifi_status() -> u32; 24 | #[link_name = "wifiLocalIp"] fn unsafe_wifi_local_ip(buf : *mut u8); 25 | #[link_name = "printWifiLocalIp"] fn unsafe_print_wifi_local_ip(); 26 | } 27 | 28 | pub static LOW:u32 = 0; 29 | pub static HIGH:u32 = 1; 30 | 31 | pub static INPUT:u32 = 0x0; 32 | pub static OUTPUT:u32 = 0x1; 33 | pub static INPUT_PULLUP:u32 = 0x2; 34 | 35 | /* Wifi Status */ 36 | pub static WL_NO_SHIELD: u32 = 255; 37 | pub static WL_IDLE_STATUS: u32 = 0; 38 | pub static WL_NO_SSID_AVAIL: u32 = 1; 39 | pub static WL_SCAN_COMPLETED: u32 = 2; 40 | pub static WL_CONNECTED: u32 = 0x03; 41 | pub static WL_CONNECT_FAILED: u32 = 4; 42 | pub static WL_CONNECTION_LOST: u32 = 5; 43 | pub static WL_DISCONNECTED: u32 = 6; 44 | 45 | pub fn millis () -> u32 { unsafe { unsafe_millis() } } 46 | pub fn delay (ms: u32) { unsafe { unsafe_delay(ms); } } 47 | pub fn pinMode (pin:u32, mode:u32) { unsafe { unsafe_pinMode(pin, mode) } } 48 | pub fn digitalWrite (pin:u32, value:u32) { unsafe { unsafe_digitalWrite(pin, value) } } 49 | pub fn serialPrintInt(out: u32) { unsafe { unsafe_print_int(out); } } 50 | pub fn serialPrint (out: &str) { 51 | unsafe { 52 | unsafe_print(out.as_bytes().as_ptr() as *const u8, out.len()); 53 | } 54 | } 55 | 56 | pub fn serialPrintln (out: &str) { 57 | serialPrint(out); 58 | serialPrint("\n"); 59 | } 60 | 61 | pub fn getPinLED () -> u32 { unsafe { unsafe_getPinLED() } } 62 | 63 | pub fn wifiConnect (ssid :&str, password : &str){ 64 | unsafe { 65 | unsafe_wifi_connect( 66 | ssid.as_bytes().as_ptr() as *const u8, ssid.len(), 67 | password.as_bytes().as_ptr() as *const u8, password.len() 68 | ); 69 | } 70 | } 71 | pub fn wifiStatus () -> u32 { unsafe { unsafe_wifi_status() } } 72 | pub fn printWifiLocalIp () { unsafe { unsafe_print_wifi_local_ip() } } -------------------------------------------------------------------------------- /rust/build.sh: -------------------------------------------------------------------------------- 1 | # Prepare 2 | #rustup target add wasm32-unknown-unknown 3 | 4 | # Compile 5 | rustc -O --crate-type=cdylib \ 6 | --target=wasm32-unknown-unknown \ 7 | -C link-arg=-zstack-size=2048 \ 8 | -C link-arg=-s \ 9 | -o app.wasm app.rs 10 | 11 | # Optimize (optional) 12 | #wasm-opt -O3 app.wasm -o app.wasm 13 | #wasm-strip app.wasm 14 | 15 | # Convert to WAT 16 | #wasm2wat app.wasm -o app.wat 17 | 18 | # Convert to C header 19 | xxd -i app.wasm > app.wasm.h 20 | 21 | cp app.wasm ../data/app.wasm -------------------------------------------------------------------------------- /src/m3_arduino_api.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "m3_arduino_api.h" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #ifdef ESP32 9 | #include 10 | #elif defined(ESP8266) 11 | #include 12 | #endif 13 | 14 | /* 15 | * Note: each RawFunction should complete with one of these calls: 16 | * m3ApiReturn(val) - Returns a value 17 | * m3ApiSuccess() - Returns void (and no traps) 18 | * m3ApiTrap(trap) - Returns a trap 19 | */ 20 | 21 | m3ApiRawFunction(m3_arduino_millis) 22 | { 23 | m3ApiReturnType(uint32_t); 24 | m3ApiReturn(millis()); 25 | } 26 | 27 | m3ApiRawFunction(m3_arduino_delay) 28 | { 29 | m3ApiGetArg(uint32_t, ms); 30 | 31 | delay(ms); 32 | 33 | m3ApiSuccess(); 34 | } 35 | 36 | // This maps pin modes from arduino_wasm_api.h 37 | // to actual platform-specific values 38 | uint8_t mapPinMode(uint8_t mode) 39 | { 40 | switch (mode) 41 | { 42 | case (0): 43 | return INPUT; 44 | case (1): 45 | return OUTPUT; 46 | case (2): 47 | return INPUT_PULLUP; 48 | } 49 | return INPUT; 50 | } 51 | 52 | m3ApiRawFunction(m3_arduino_pinMode) 53 | { 54 | m3ApiGetArg(uint32_t, pin); 55 | m3ApiGetArg(uint32_t, mode); 56 | 57 | pinMode(pin, mapPinMode(mode)); 58 | 59 | m3ApiSuccess(); 60 | } 61 | 62 | m3ApiRawFunction(m3_arduino_digitalWrite) 63 | { 64 | m3ApiGetArg(uint32_t, pin); 65 | m3ApiGetArg(uint32_t, value); 66 | 67 | digitalWrite(pin, value); 68 | 69 | m3ApiSuccess(); 70 | } 71 | 72 | // This is a convenience function 73 | m3ApiRawFunction(m3_arduino_getPinLED) 74 | { 75 | m3ApiReturnType(uint32_t); 76 | m3ApiReturn(LED_BUILTIN); 77 | } 78 | 79 | m3ApiRawFunction(m3_arduino_print) 80 | { 81 | m3ApiGetArgMem(const byte *, out); 82 | m3ApiGetArg(int32_t, out_len); 83 | 84 | //printf("api: log %p\n", out); 85 | 86 | byte buff[out_len + 1]; 87 | memcpy(buff, out, out_len); 88 | buff[out_len] = '\0'; 89 | 90 | Serial.print((char *)buff); 91 | 92 | m3ApiSuccess(); 93 | } 94 | 95 | m3ApiRawFunction(m3_arduino_print_int) 96 | { 97 | m3ApiGetArg(int32_t, out); 98 | 99 | Serial.print(out); 100 | 101 | m3ApiSuccess(); 102 | } 103 | 104 | m3ApiRawFunction(m3_arduino_wifi_status) 105 | { 106 | m3ApiReturnType(uint32_t) 107 | 108 | m3ApiReturn(WiFi.status()); 109 | } 110 | 111 | m3ApiRawFunction(m3_arduino_wifi_connect) 112 | { 113 | m3ApiGetArgMem(const byte *, ssid); 114 | m3ApiGetArg(int32_t, ssid_len); 115 | m3ApiGetArgMem(const byte *, pass); 116 | m3ApiGetArg(int32_t, pass_len); 117 | 118 | byte local_ssid[ssid_len + 1]; 119 | byte local_password[pass_len + 1]; 120 | 121 | memcpy(local_ssid, ssid, ssid_len); 122 | local_ssid[ssid_len] = '\0'; 123 | memcpy(local_password, pass, pass_len); 124 | local_password[pass_len] = '\0'; 125 | 126 | WiFi.begin((char *)local_ssid, (char *)local_password); 127 | 128 | m3ApiSuccess(); 129 | } 130 | 131 | m3ApiRawFunction(m3_arduino_getChipID) 132 | { 133 | m3ApiGetArgMem(char *, out); 134 | 135 | uint64_t chipid = ESP.getEfuseMac(); 136 | char chipIdString[12]; 137 | sprintf(chipIdString, "esp-%08X", (uint32_t)chipid); 138 | uint32_t len = strlen(chipIdString); 139 | memcpy(out, chipIdString, len); 140 | out[len] = '\0'; 141 | 142 | m3ApiSuccess(); 143 | } 144 | 145 | m3ApiRawFunction(m3_arduino_wifi_local_ip) 146 | { 147 | m3ApiGetArgMem(char *, out); 148 | 149 | const char *ip = WiFi.localIP().toString().c_str(); 150 | uint32_t len = strlen(ip); 151 | memcpy(out, ip, len); 152 | out[len] = '\0'; 153 | m3ApiSuccess(); 154 | } 155 | 156 | m3ApiRawFunction(m3_arduino_wifi_print_local_ip) 157 | { 158 | Serial.print(WiFi.localIP()); 159 | 160 | m3ApiSuccess(); 161 | } 162 | 163 | // Dummy, for TinyGO 164 | m3ApiRawFunction(m3_dummy) 165 | { 166 | m3ApiSuccess(); 167 | } 168 | 169 | M3Result m3_LinkArduino(IM3Runtime runtime) 170 | { 171 | IM3Module module = runtime->modules; 172 | const char *arduino = "arduino"; 173 | const char *serial = "serial"; 174 | const char *wifi = "wifi"; 175 | const char *http = "http"; 176 | 177 | m3_LinkRawFunction(module, arduino, "millis", "i()", &m3_arduino_millis); 178 | m3_LinkRawFunction(module, arduino, "delay", "v(i)", &m3_arduino_delay); 179 | m3_LinkRawFunction(module, arduino, "pinMode", "v(ii)", &m3_arduino_pinMode); 180 | m3_LinkRawFunction(module, arduino, "digitalWrite", "v(ii)", &m3_arduino_digitalWrite); 181 | m3_LinkRawFunction(module, arduino, "getPinLED", "i()", &m3_arduino_getPinLED); 182 | m3_LinkRawFunction(module, arduino, "getChipID", "v(*)", &m3_arduino_getChipID); 183 | 184 | /* Serial */ 185 | m3_LinkRawFunction(module, serial, "print", "v(*i)", &m3_arduino_print); 186 | m3_LinkRawFunction(module, serial, "printInt", "v(i)", &m3_arduino_print_int); 187 | 188 | /* Wifi */ 189 | m3_LinkRawFunction(module, wifi, "wifiStatus", "i()", &m3_arduino_wifi_status); 190 | m3_LinkRawFunction(module, wifi, "wifiConnect", "v(*i*i)", &m3_arduino_wifi_connect); 191 | m3_LinkRawFunction(module, wifi, "wifiLocalIp", "v(*)", &m3_arduino_wifi_local_ip); 192 | m3_LinkRawFunction(module, wifi, "printWifiLocalIp", "v()", &m3_arduino_wifi_print_local_ip); 193 | 194 | // Dummy (for TinyGo) 195 | m3_LinkRawFunction(module, "env", "io_get_stdout", "i()", &m3_dummy); 196 | 197 | return m3Err_none; 198 | } 199 | -------------------------------------------------------------------------------- /src/m3_arduino_api.h: -------------------------------------------------------------------------------- 1 | #ifndef m3_arduino_api_h 2 | #define m3_arduino_api_h 3 | 4 | #include 5 | 6 | #if defined(__cplusplus) 7 | extern "C" 8 | { 9 | #endif 10 | 11 | M3Result m3_LinkArduino(IM3Runtime runtime); 12 | 13 | #if defined(__cplusplus) 14 | } 15 | #endif 16 | 17 | #endif -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Wasm3 - high performance WebAssembly interpreter written in C. 3 | // 4 | // Copyright © 2019 Steven Massey, Volodymyr Shymanskyy. 5 | // All rights reserved. 6 | // 7 | 8 | #include "Arduino.h" 9 | #ifdef ESP32 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #elif defined(ESP8266) 16 | #include 17 | #include 18 | #include 19 | #endif 20 | #include 21 | #include 22 | #include "m3_arduino_api.h" 23 | 24 | #define WASM_STACK_SLOTS 2 * 1024 25 | #define NATIVE_STACK_SIZE 32 * 1024 26 | 27 | AsyncWebServer server(80); 28 | // AssemblyScript app 29 | // #include "../assemblyscript/app.wasm.h" 30 | 31 | // Rust app 32 | //#include "../rust/app.wasm.h" 33 | 34 | #define FATAL_MSG(func, msg) \ 35 | { \ 36 | Serial.print("Fatal: " func " "); \ 37 | Serial.println(msg); \ 38 | delay(1000); \ 39 | } 40 | 41 | #define FATAL(func, msg) \ 42 | { \ 43 | FATAL_MSG(func, msg) \ 44 | continue; \ 45 | } 46 | 47 | size_t readWasmFileSize(const char *path) 48 | { 49 | Serial.printf("Reading file: %s\n", path); 50 | 51 | if (!SPIFFS.exists(path)) 52 | { 53 | Serial.println("File not found"); 54 | return 0; 55 | } 56 | 57 | File file = SPIFFS.open(path); 58 | if (!file) 59 | { 60 | Serial.println("Failed to open file for reading"); 61 | return 0; 62 | } 63 | size_t size = file.size(); 64 | file.close(); 65 | return size; 66 | } 67 | 68 | size_t readWasmFile(const char *path, uint8_t *buf) 69 | { 70 | Serial.printf("Reading file: %s\n", path); 71 | 72 | if (!SPIFFS.exists(path)) 73 | { 74 | Serial.println("File not found"); 75 | return 0; 76 | } 77 | 78 | File file = SPIFFS.open(path); 79 | if (!file) 80 | { 81 | Serial.println("Failed to open file for reading"); 82 | return 0; 83 | } 84 | 85 | Serial.println("Read from file: "); 86 | size_t i = 0; 87 | while (file.available()) 88 | { 89 | buf[i] = file.read(); 90 | i++; 91 | } 92 | file.close(); 93 | 94 | return i; 95 | } 96 | 97 | void wasm_task(void *) 98 | { 99 | IM3Environment env = m3_NewEnvironment(); 100 | if (!env) 101 | { 102 | FATAL_MSG("NewEnvironment", "failed"); 103 | return; 104 | } 105 | 106 | IM3Runtime runtime = m3_NewRuntime(env, WASM_STACK_SLOTS, NULL); 107 | if (!runtime) 108 | { 109 | FATAL_MSG("NewRuntime", "failed"); 110 | return; 111 | } 112 | 113 | while (1) 114 | { 115 | M3Result result = m3Err_none; 116 | 117 | size_t app_wasm_size = readWasmFileSize("/app.wasm"); 118 | if (app_wasm_size == 0) 119 | FATAL("ReadWasm", "File not found") 120 | 121 | uint8_t buffer[app_wasm_size]; 122 | size_t read_bytes = readWasmFile("/app.wasm", buffer); 123 | if (read_bytes == 0) 124 | FATAL("ReadWasm", "File not found") 125 | 126 | IM3Module module; 127 | result = m3_ParseModule(env, &module, buffer, app_wasm_size); 128 | if (result) 129 | FATAL("ParseModule", result); 130 | 131 | result = m3_LoadModule(runtime, module); 132 | if (result) 133 | FATAL("LoadModule", result); 134 | 135 | result = m3_LinkArduino(runtime); 136 | if (result) 137 | FATAL("LinkArduino", result); 138 | 139 | IM3Function f; 140 | result = m3_FindFunction(&f, runtime, "_start"); 141 | if (result) 142 | FATAL("FindFunction", result); 143 | 144 | printf("Running WebAssembly...\n"); 145 | 146 | result = m3_CallV(f); 147 | 148 | // Should not arrive here 149 | 150 | if (result) 151 | { 152 | M3ErrorInfo info; 153 | m3_GetErrorInfo(runtime, &info); 154 | Serial.print("Error: "); 155 | Serial.print(result); 156 | Serial.print(" ("); 157 | Serial.print(info.message); 158 | Serial.println(")"); 159 | } 160 | } 161 | } 162 | 163 | void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) 164 | { 165 | if (!filename.endsWith(".wasm")) 166 | { 167 | request->send(400, "text/plain", "Only wasm files accepted"); 168 | return; 169 | } 170 | if (!index) 171 | { 172 | Serial.printf("UploadStart: %s\n", filename.c_str()); 173 | request->_tempFile = SPIFFS.open("/app.wasm", "w"); 174 | } 175 | if (len) 176 | { 177 | request->_tempFile.write(data, len); 178 | } 179 | //Serial.printf("%s", (const char *)data); 180 | if (final) 181 | { 182 | request->_tempFile.close(); 183 | Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index + len); 184 | request->send(200, "text/plain", "Uploaded"); 185 | delay(1000); 186 | ESP.restart(); 187 | } 188 | } 189 | 190 | void setup() 191 | { 192 | Serial.begin(115200); 193 | delay(100); 194 | 195 | SPIFFS.begin(true); 196 | delay(100); 197 | 198 | WiFi.mode(WIFI_AP_STA); 199 | WiFi.softAP("WasmOTA"); 200 | 201 | uint64_t chipid = ESP.getEfuseMac(); 202 | char hostname[20]; 203 | sprintf(hostname, "esp-%08X", (uint32_t)chipid); 204 | 205 | MDNS.begin(hostname); 206 | server.on( 207 | "/upload", HTTP_POST, [](AsyncWebServerRequest *request) {}, handleUpload); 208 | server.begin(); 209 | MDNS.addService("ota", "tcp", 80); 210 | 211 | Serial.print("\nWasm3 v" M3_VERSION ", build " __DATE__ " " __TIME__ "\n"); 212 | Serial.print(hostname); 213 | 214 | #ifdef ESP32 215 | // On ESP32, we can launch in a separate thread 216 | // xTaskCreate(&wasm_task, "wasm3", NATIVE_STACK_SIZE, NULL, 5, NULL); 217 | xTaskCreatePinnedToCore(&wasm_task, "wasm3", NATIVE_STACK_SIZE, NULL, 5, NULL, 1); 218 | #else 219 | wasm_task(NULL); 220 | #endif 221 | } 222 | 223 | void loop() 224 | { 225 | delay(100); 226 | } 227 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /tinygo/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | 6 | . "wasm-wifi/arduino" 7 | serial "wasm-wifi/serial" 8 | wifi "wasm-wifi/wifi" 9 | ) 10 | 11 | var ledPin = GetPinLED() 12 | var ledState = false 13 | var lastMillis uint 14 | var deviceID = "" 15 | 16 | const ( 17 | blinkInterval = 1000 18 | ssid = "YOUR_SSID" 19 | password = "YOUR_PASSWORD" 20 | ) 21 | 22 | func setup() { 23 | PinMode(ledPin, OUTPUT) 24 | 25 | DigitalWrite(ledPin, LOW) 26 | lastMillis = Millis() 27 | serial.Println("Hello from TinyGo 🐹") 28 | deviceID = GetChipID() 29 | } 30 | 31 | func connect() { 32 | if wifi.Status() == wifi.WL_CONNECTED { 33 | return 34 | } 35 | 36 | wifi.Connect(ssid, password) 37 | serial.Println("Connecting") 38 | attempts := 0 39 | for wifi.Status() != wifi.WL_CONNECTED { 40 | Delay(500) 41 | serial.Print(".") 42 | attempts++ 43 | if attempts >= 10 { 44 | serial.Println("Failed to connect!") 45 | return 46 | } 47 | } 48 | serial.Println("Connected!") 49 | serial.Println(wifi.LocalIp()) 50 | } 51 | 52 | func loop() { 53 | connect() 54 | currentMillis := Millis() 55 | if (currentMillis - lastMillis) >= blinkInterval { 56 | connected := wifi.Status() == wifi.WL_CONNECTED 57 | localIP := wifi.LocalIp() 58 | 59 | serial.Print("[") 60 | serial.Print(strconv.FormatUint(uint64(currentMillis), 10)) 61 | serial.Print("] [" + deviceID) 62 | serial.Print("] [connected : ") 63 | serial.Print(strconv.FormatBool(connected)) 64 | serial.Print("] [" + localIP + "] ") 65 | serial.Println("TinyGo 🐹") 66 | 67 | ledState = !ledState 68 | if ledState { 69 | DigitalWrite(ledPin, HIGH) 70 | } else { 71 | DigitalWrite(ledPin, LOW) 72 | } 73 | 74 | lastMillis = Millis() 75 | } 76 | Delay(10) 77 | } 78 | 79 | /* 80 | * Entry point 81 | */ 82 | func main() { 83 | setup() 84 | for { 85 | loop() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tinygo/arduino/arduino.go: -------------------------------------------------------------------------------- 1 | package arduino 2 | 3 | const ( 4 | LOW = 0 5 | HIGH = 1 6 | 7 | INPUT = 0 8 | OUTPUT = 1 9 | INPUT_PULLUP = 2 10 | ) 11 | 12 | //go:wasm-module arduino 13 | //go:export millis 14 | func Millis() uint 15 | 16 | //go:wasm-module arduino 17 | //go:export delay 18 | func Delay(ms uint) 19 | 20 | //go:wasm-module arduino 21 | //go:export pinMode 22 | func PinMode(pin, mode uint) 23 | 24 | //go:wasm-module arduino 25 | //go:export digitalWrite 26 | func DigitalWrite(pin, value uint) 27 | 28 | //go:wasm-module arduino 29 | //go:export getPinLED 30 | func GetPinLED() uint 31 | 32 | //go:wasm-module arduino 33 | //go:export getChipID 34 | func _getChipID(buf *byte) 35 | 36 | func GetChipID() string { 37 | var buf = make([]byte, 16) 38 | _getChipID(&buf[0]) 39 | // Find '\0' 40 | n := -1 41 | for i, b := range buf { 42 | if b == 0 { 43 | break 44 | } 45 | n = i 46 | } 47 | return string(buf[:n+1]) 48 | } 49 | -------------------------------------------------------------------------------- /tinygo/build.sh: -------------------------------------------------------------------------------- 1 | # Prepare 2 | #export PATH=/usr/local/tinygo/bin:$PATH 3 | #export GOROOT=/opt/go 4 | 5 | # Compile 6 | tinygo build -target wasm --no-debug \ 7 | -panic trap -wasm-abi generic \ 8 | -ldflags="-z stack-size=4096 --max-memory=65536" \ 9 | -heap-size 4096 \ 10 | -o app.wasm app.go 11 | 12 | # Optimize (optional) 13 | #wasm-opt -O3 app.wasm -o app.wasm 14 | #wasm-strip app.wasm 15 | 16 | # Convert to WAT 17 | #wasm2wat app.wasm -o app.wat 18 | 19 | # Convert to C header 20 | xxd -i app.wasm > app.wasm.h 21 | 22 | cp app.wasm ../data/app.wasm -------------------------------------------------------------------------------- /tinygo/go.mod: -------------------------------------------------------------------------------- 1 | module wasm-wifi 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /tinygo/serial/serial.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | //go:wasm-module serial 4 | //go:export print 5 | func Print(s string) 6 | 7 | func Println(s string) { 8 | Print(s) 9 | Print("\n") 10 | } 11 | -------------------------------------------------------------------------------- /tinygo/wifi/wifi.go: -------------------------------------------------------------------------------- 1 | package wifi 2 | 3 | //go:wasm-module wifi 4 | //go:export wifiStatus 5 | func Status() uint 6 | 7 | //go:wasm-module wifi 8 | //go:export wifiConnect 9 | func Connect(ssid string, password string) 10 | 11 | //go:wasm-module wifi 12 | //go:export wifiLocalIp 13 | func _getLocalIp(buf *byte) 14 | 15 | func LocalIp() string { 16 | var buf = make([]byte, 16) 17 | _getLocalIp(&buf[0]) 18 | // Find '\0' 19 | n := -1 20 | for i, b := range buf { 21 | if b == 0 { 22 | break 23 | } 24 | n = i 25 | } 26 | return string(buf[:n+1]) 27 | } 28 | 29 | const ( 30 | WL_NO_SHIELD = 0x255 31 | WL_IDLE_STATUS = 0x0 32 | WL_NO_SSID_AVAIL = 0x1 33 | WL_SCAN_COMPLETED = 0x2 34 | WL_CONNECTED = 0x3 35 | WL_CONNECT_FAILED = 0x4 36 | WL_CONNECTION_LOST = 0x5 37 | WL_DISCONNECTED = 0x6 38 | ) 39 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wasm on the Browser 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 |

26 | Wasm Arduino 27 |

28 |
29 |
30 |
31 | LED 32 |
33 |
34 |
35 | 38 |
39 | 46 |
47 | 48 | 49 |
50 |
51 |
52 |
53 | 56 |
57 | 63 |
64 | 65 | 66 |
67 |
68 |
69 |
70 | 73 | 76 |
77 |
78 | 81 | 84 |
85 |
86 |
87 |

88 | 89 |

90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /web/index.js: -------------------------------------------------------------------------------- 1 | import "babel-polyfill" 2 | 3 | let source = 'as.wasm' 4 | let ipAddr = '127.0.0.1' 5 | let status = 'offline' 6 | 7 | let worker = null 8 | 9 | const serialOutput = document.getElementById('serial') 10 | const ssidOutput = document.getElementById('ssid') 11 | const ledOutput = document.getElementById('led') 12 | 13 | const sourceSelect = document.getElementById('source') 14 | const statusSelect = document.getElementById('wifiStatus') 15 | const ipAddrInput = document.getElementById('ipaddr') 16 | 17 | sourceSelect.onchange = function(event){ 18 | console.log('Source', event.target.value) 19 | source = event.target.value 20 | start() 21 | } 22 | 23 | statusSelect.onchange = function(event){ 24 | console.log('Status', event.target.value) 25 | status = event.target.value 26 | worker.postMessage({ type : 'setStatus', payload : status }) 27 | } 28 | 29 | ipAddrInput.onblur = function(event){ 30 | console.log('ipAddr', event.target.value) 31 | ipAddr = event.target.value 32 | worker.postMessage({ type : 'setIpAddr', payload : ipAddr }) 33 | } 34 | 35 | async function start(){ 36 | if(worker){ 37 | worker.terminate() 38 | ssidOutput.value = '' 39 | statusSelect.value = 'offline' 40 | } 41 | 42 | worker = new Worker('./worker.js') 43 | 44 | const wasmModule = await createWebAssembly(source); 45 | worker.postMessage({ type : 'init', payload : { module : wasmModule, ipAddr, status } }) 46 | worker.postMessage({ type : 'setStatus', payload : status }) 47 | worker.postMessage({ type : 'setIpAddr', payload : ipAddr }) 48 | 49 | worker.onmessage = (msg) => { 50 | //console.log('main on msg', msg); 51 | switch(msg.data.type) { 52 | case 'digitalWrite': { 53 | const { pin , value } = msg.data.payload 54 | ledOutput.textContent = value ? 'ON' : 'OFF' 55 | //ledOutput.style['color'] = value ? 'black' : 'white' 56 | ledOutput.style['background-color'] = value ? 'green' : 'black' 57 | break; 58 | } 59 | case 'print' : { 60 | const { output } = msg.data.payload 61 | const finalOutput = output.replace("\n", "
") 62 | serialOutput.innerHTML += finalOutput 63 | serialOutput.scrollTo(0,serialOutput.scrollHeight); 64 | break; 65 | } 66 | case 'wifiConnect' : { 67 | const { ssid } = msg.data.payload 68 | ssidOutput.value = ssid 69 | statusSelect.value = 'online' 70 | break; 71 | } 72 | } 73 | } 74 | } 75 | 76 | async function createWebAssembly(path) { 77 | const result = await window.fetch(path); 78 | const bytes = await result.arrayBuffer(); 79 | //const txt = await result.text(); 80 | //console.log(txt) 81 | return WebAssembly.compile(bytes); 82 | } 83 | 84 | start() -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-arduino-wifi-web", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npx parcel index.html", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "parcel-bundler": "^1.12.4", 15 | "parcel-plugin-static-files-copy": "^2.3.1" 16 | }, 17 | "dependencies": { 18 | "babel-polyfill": "^6.26.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/worker.js: -------------------------------------------------------------------------------- 1 | 2 | const memory = new WebAssembly.Memory({initial: 256, maximum: 2048}); 3 | let exports = null; 4 | let linearMemory = null; 5 | 6 | let defaultStatus = 'offline' 7 | let defaultIpAddr = '127.0.0.1' 8 | 9 | function init(wasmModule, opts) { 10 | ipAddr = (opts && opts.ipAddr) || defaultIpAddr 11 | status = defaultStatus 12 | const env = { 13 | 'abortStackOverflow': _ => { throw new Error('overflow'); }, 14 | 'table': new WebAssembly.Table({initial: 0, maximum: 0, element: 'anyfunc'}), 15 | 'tableBase': 0, 16 | 'memory': memory, 17 | 'memoryBase': 1024, 18 | 'STACKTOP': 0, 19 | 'STACK_MAX': memory.buffer.byteLength, 20 | 'io_get_stdout' : () => {} 21 | }; 22 | 23 | const arduino = { 24 | digitalWrite : (pin, value ) => { 25 | postMessage({ type : 'digitalWrite', payload : { pin, value }}) 26 | }, 27 | millis: () => { 28 | return Date.now() % 10000000 29 | }, 30 | delay: (ms) => { 31 | //postMessage({ type : 'delay', payload : { ms }}) 32 | }, 33 | pinMode: (pin, mode) => { 34 | postMessage({ type : 'pinMode', payload : { pin, mode }}) 35 | }, 36 | getPinLED: () => { 37 | return 42 38 | }, 39 | } 40 | 41 | const utf8decoder = new TextDecoder() 42 | const utf8encoder = new TextEncoder() 43 | 44 | const serial = { 45 | print: (index, len) => { 46 | const stringBuffer = new Uint8Array(linearMemory.buffer, index,len); 47 | const output = utf8decoder.decode(stringBuffer); 48 | postMessage({ type : 'print', payload : { output }}) 49 | }, 50 | printInt: (value) => { 51 | postMessage({ type : 'print', payload : { output: String(value) }}) 52 | } 53 | } 54 | 55 | const wifi = { 56 | wifiStatus: () => { 57 | return status === 'online' ? 58 | 0x3 : // WL_CONNECTED 59 | 0x6 // WL_DISCONNECTED 60 | }, 61 | wifiConnect: (ssid_index, ssid_len, password_index, pass_len) => { 62 | //console.log('Wifi Connect', ssid, pass) 63 | const ssidBuffer = new Uint8Array(linearMemory.buffer, ssid_index,ssid_len); 64 | const passBuffer = new Uint8Array(linearMemory.buffer, password_index,pass_len); 65 | const ssid = utf8decoder.decode(ssidBuffer); 66 | const password = utf8decoder.decode(passBuffer); 67 | status = 'online' 68 | postMessage({ type : 'wifiConnect', payload : { ssid, password }}) 69 | }, 70 | printWifiLocalIp : () => { 71 | const output = ipAddr 72 | postMessage({ type : 'print', payload : { output }}) 73 | }, 74 | wifiLocalIp: (index) => { 75 | const stringBuffer = new Uint8Array(linearMemory.buffer, index, 16); 76 | utf8encoder.encodeInto(ipAddr, stringBuffer) 77 | } 78 | } 79 | 80 | const importObject = { env, arduino, serial, wifi, memory }; 81 | WebAssembly.instantiate(wasmModule, importObject) 82 | .then( wasmInstance => { 83 | console.log(wasmInstance) 84 | exports = wasmInstance.exports 85 | linearMemory = exports.memory; 86 | console.info('got exports', exports, wasmInstance); 87 | if(exports.cwa_main){ 88 | exports.cwa_main(); 89 | }else{ 90 | exports._start(); 91 | } 92 | }) 93 | } 94 | 95 | console.log('init worker') 96 | 97 | setStatus = function(newStatus){ 98 | status = newStatus 99 | } 100 | 101 | onmessage = function(msg){ 102 | console.log('on msg worker', msg) 103 | if(msg.data.type === 'init'){ 104 | init(msg.data.payload.module, msg.data.payload) 105 | } 106 | if(msg.data.type === 'setIpAddr'){ 107 | ipAddr = msg.data.payload 108 | } 109 | if(msg.data.type === 'setStatus'){ 110 | status = msg.data.payload 111 | } 112 | } --------------------------------------------------------------------------------