From ade4aa96c4176aeb3340ce43dcb4c00fbccd1ae4 Mon Sep 17 00:00:00 2001 From: Rob Anderson Date: Tue, 13 Aug 2024 13:05:29 +0100 Subject: [PATCH 1/7] rename to ScriptableThings --- README.md | 26 +++++++++---------- examples/captive-portal/platformio.ini | 2 +- examples/captive-portal/src/main.cpp | 2 +- library.json | 6 ++--- library.properties | 6 ++--- ...rogrammableThings.h => ScriptableThings.h} | 0 src/engine/Program.h | 4 +-- src/engine/ProgramEngine.h | 8 +++--- src/engine/ProgramScheduler.h | 2 +- src/server/CaptivePortal.h | 2 +- 10 files changed, 29 insertions(+), 29 deletions(-) rename src/{ProgrammableThings.h => ScriptableThings.h} (100%) diff --git a/README.md b/README.md index c84cc49..c512ab4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# ProgrammableThings +# ScriptableThings -ProgrammableThings is an Arduino library for building IoT devices that can easily be reprogrammed and reconfigured by users with JavaScript. It introduces a new malleable layer allowing dynamic scripting on devices, instead of all functionality that is statically baked into the firmware. The library enables you to quickly create an API between your hardware and the JavaScript scripting layer. We hope this makes IoT more maintainable over time and more resilient to vanishing cloud services. +ScriptableThings is an Arduino library for building IoT devices that can easily be reprogrammed and reconfigured by administrators or users with standards-based JavaScript. It introduces a new malleable layer allowing dynamic scripting on devices, instead of functionality being baked into the firmware. The library enables you to create an API between your hardware and the scripting layer. We hope this makes IoT more maintainable over time, more resilient to vanishing cloud services and more-safely hackable devices. ## Dependencies The project has no dependencies but it does bundle [QuickJS](https://bellard.org/quickjs/) for the JavaScript engine and a runtime is created on top of that. The HTTP logic is designed to be used with [me-no-dev/ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) so you may want to install that, but it is not required. -ProgrammableThings currently supports ESP32 development boards, we're working on ESP8266 devices and we're interested in the RP2040 chip too. +ScriptableThings currently supports ESP32 development boards, we're working on ESP8266 devices and we're interested in the RP2040 chip too. ## Install @@ -17,28 +17,28 @@ The easiest way to get started is with [PlatformIO](https://platformio.org/) whi ```ini [env:xxx] lib_deps = - git@github.com:robb-j/ProgrammableThings.git#v0.1.0 + git@github.com:digitalinteraction/ScriptableThings.git#v0.1.0 ``` Or alternatively you can clone or submodule the repository into your `libs` folder. ### Arduino IDE -You can download a zip of the latest from the [GitHub releases](https://github.com/robb-j/ProgrammableThings/releases) and use Arduino IDE's **library manager** feature to install it globally. +You can download a zip of the latest from the [GitHub releases](https://github.com/digitalinteraction/ScriptableThings/releases) and use Arduino IDE's **library manager** feature to install it globally. ## Usage -When you want to use ProgrammableThings, you can import any of the header files for specific features you want or there is a catch-all import `ProgrammableThings.h` which includes everything. +When you want to use ScriptableThings, you can import any of the header files for specific features you want or there is a catch-all import `ScriptableThings.h` which includes everything. ```cpp #include -#include +#include #include #include auto server = AsyncWebServer(80); -auto portal = CaptivePortal("ProgThing Portal"); +auto portal = CaptivePortal("ScriptableThing Portal"); auto engine = ProgramEngine(&SPIFFS, "/scripts/", ESP.getFreeHeap() >> 1, JavaScript::setup); ``` @@ -58,15 +58,15 @@ TODO: write more docs on how to do JavaScript stuff The setup method you pass to `ProgramEngine` lets your customise the JavaScript world the scripts will run in. This means you can inject variables, methods and objects that the JavaScript can use to interact with the hardware they are being run on. -**Private by default** — the engine is designed to reveal nothing about the hardware by default, unlike other microcontroller-JavaScript bindings. You might not necessarily trust the scripts that are being run and you don't want your device turned into a spying device. This is especially relevant because a key part of ProgrammableThings is that the scripts on the device are malleable and can change, rather than being baked in when flashing the controller. This means that any interaction with the hardware needs to be specifically designed and programmed to get it working. While this takes more work, it means you can create a cleaner API between the hardware, firmware and scripts. +**Private by default** — unlike other microcontroller-JavaScript bindings, the engine is designed to reveal nothing about the hardware by default. You might not necessarily trust the scripts that are being run and you don't want your device turned into a spying device. This is especially relevant because a key part of ScriptableThings is that the scripts on the device are malleable and can change, rather than being baked in when flashing the controller. This means that any interaction with the hardware needs to be specifically designed and programmed to get it working. While this takes more work, it means you can create a cleaner API between the hardware, firmware and scripts. The setup method is where you create an API between your hardware and the scripts that run on it and you can design that however you like. While `ProgramEngine` is starting a `Program` it will call your `setup` method to customise the JavaScript world which you use to inject your API into it. -Currently, you need to use QuickJS itself to create your API, but we're thinking about easier ways to do this in the future. For example code generation based on documentation-comments. +Currently, you need to use QuickJS itself to create your API, but we're thinking about easier ways to do this in the future. For example code generation based on documentation-comments or AST analysis. ```cpp #include -#include +#include class JavaScript { @@ -98,7 +98,7 @@ In the JavaScript world you can now call `Thing.sayHello('geoff')` and it will d #### JavaScript Runtime -Not all of the things that exist in JavaScript are in the runtime, here is a list of things you can use. QuickJS, which powers JavaScript, supports up to the ES2020 version of JavaScript. Additionally, ProgrammableThings adds: +Not all of the things that exist in JavaScript are in the runtime, here is a list of things you can use. QuickJS, which powers JavaScript, supports up to the ES2020 version of JavaScript. Additionally, ScriptableThings adds: - `console.log` - `setTimeout` @@ -117,7 +117,7 @@ You will want the JavaScript world to talk to you hardware in some way and these This pointer is passed on to any Program that is created too so you can access it on the Program via `Program#getOpaque`. The pointer is then in-turn set on QuickJS's `JSContext` object for you to access in js-c method bindings. ```cpp -#include +#include #include "AppContext.h" class JavaScript diff --git a/examples/captive-portal/platformio.ini b/examples/captive-portal/platformio.ini index 6dab91d..1fe4f3e 100644 --- a/examples/captive-portal/platformio.ini +++ b/examples/captive-portal/platformio.ini @@ -19,7 +19,7 @@ lib_deps = me-no-dev/AsyncTCP@^1.1.1 me-no-dev/ESPAsyncTCP@^1.2.2 https://github.com/me-no-dev/ESPAsyncWebServer.git#f71e3d427b5be9791a8a2c93cf8079792c3a9a26 - git@github.com:robb-j/ProgrammableThings.git#dd24789c5a52810b9b525c56ab515a2efd1cc05a + git@github.com:digitalinteraction/ScriptableThings.git#dd24789c5a52810b9b525c56ab515a2efd1cc05a build_flags= -D ESP32 -D DEBUG diff --git a/examples/captive-portal/src/main.cpp b/examples/captive-portal/src/main.cpp index 85e1ece..dd2fe04 100644 --- a/examples/captive-portal/src/main.cpp +++ b/examples/captive-portal/src/main.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/library.json b/library.json index b7f2378..6e3fd14 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { - "name": "ProgrammableThings", - "description": "Reprogrammable stuff on microcontrollers", + "name": "ScriptableThings", + "description": "Empower things with scripts", "keywords": "", "authors": { "name": "Rob Anderson", @@ -8,7 +8,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/robb-j/ProgrammableThings" + "url": "https://github.com/digitalinteraction/ScriptableThings" }, "version": "0.1.0", "license": "MIT", diff --git a/library.properties b/library.properties index 049aac0..6fc7c95 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ -name=ProgrammableThings +name=ScriptableThings version=0.1.0 author=Rob Anderson maintainer=Rob Anderson -sentence=Reconfigurable stuff on microcontrollers +sentence=Empower things with scripts paragraph= category=Uncategorized -url=https://github.com/robb-j/ProgrammableThings +url=https://github.com/digitalinteraction/ScriptableThings architectures=esp32,esp8266 diff --git a/src/ProgrammableThings.h b/src/ScriptableThings.h similarity index 100% rename from src/ProgrammableThings.h rename to src/ScriptableThings.h diff --git a/src/engine/Program.h b/src/engine/Program.h index c23b953..395c4bb 100644 --- a/src/engine/Program.h +++ b/src/engine/Program.h @@ -58,14 +58,14 @@ class Program This is useful in your JavaScript bindings so the code that is called from the JavaScript land can dereference this value and interact with the firmware in some way. - For more information see [JavaScript](https://github.com/robb-j/ProgrammableThings#javascript). + For more information see [JavaScript](https://github.com/digitalinteraction/ScriptableThings#javascript). */ void setOpaque(void *ptr) { opaque = ptr; } /* Get the program's "opaque" value and cast it back to it's original type. More info at `Program#setOpaque` - For more information see [JavaScript](https://github.com/robb-j/ProgrammableThings#javascript). + For more information see [JavaScript](https://github.com/digitalinteraction/ScriptableThings#javascript). */ template T *getOpaque() { return static_cast(opaque); } diff --git a/src/engine/ProgramEngine.h b/src/engine/ProgramEngine.h index ec51ee1..c9a5377 100644 --- a/src/engine/ProgramEngine.h +++ b/src/engine/ProgramEngine.h @@ -31,7 +31,7 @@ class ProgramEngine ProgramEngine encapsulates a JavaScript runtime, powered by [QuickJS](https://bellard.org/quickjs/), to run scripts on microcontrollers from some sort of file system, e.g. SD or SPIFFS. You give a filesystem and directory within it to work with, how much memory to allocate for the JavaScript world and a pointer to a function to customise newly created JavaScript contexts. ```cpp - #include + #include void setupJavaScript(JSRuntime *rt, JSContext *ctx, JSValue global) { @@ -46,7 +46,7 @@ class ProgramEngine The setup method is the interesting bit, it provides a hook to let you add custom stuff in the scripts. Here it adds a new object "thing" that the JavaScript can access. - For more information see [JavaScript](https://github.com/robb-j/ProgrammableThings#javascript). + For more information see [JavaScript](https://github.com/digitalinteraction/ScriptableThings#javascript). */ ProgramEngine(fs::FS *fs, String dir, uint32_t memoryLimit, EngineCallback *setup) : fs(fs), dir(dir), memoryLimit(memoryLimit), setupCallback(setup) {} virtual ~ProgramEngine() {} @@ -94,14 +94,14 @@ class ProgramEngine Our best practise is to have a "context" object, like `AppContext` which is just an object with pointers to all the useful things your app is using. Then you can use this as your "opaque" value and have access to everything you need from your JavaScript handlers. - For more information see [JavaScript](https://github.com/robb-j/ProgrammableThings#javascript). + For more information see [JavaScript](https://github.com/digitalinteraction/ScriptableThings#javascript). */ void setOpaque(void *ptr) { opaque = ptr; } /* Retrieve your "opaque" object, set from `setOpaque`. ProgramEngine#setOpaque - For more information see [JavaScript](https://github.com/robb-j/ProgrammableThings#javascript) + For more information see [JavaScript](https://github.com/digitalinteraction/ScriptableThings#javascript) */ void *getOpaque() { return opaque; } diff --git a/src/engine/ProgramScheduler.h b/src/engine/ProgramScheduler.h index 85c1379..3143c59 100644 --- a/src/engine/ProgramScheduler.h +++ b/src/engine/ProgramScheduler.h @@ -33,7 +33,7 @@ class ProgramScheduler ProgramScheduler is responsible for managing & processing timers from the JavaScript world. ```cpp - #include + #include auto scheduler = new ProgramScheduler(context); ``` diff --git a/src/server/CaptivePortal.h b/src/server/CaptivePortal.h index d692b69..326d9a6 100644 --- a/src/server/CaptivePortal.h +++ b/src/server/CaptivePortal.h @@ -27,7 +27,7 @@ class CaptivePortal A module for creating a captive portal using the [WiFi interface](https://www.arduino.cc/reference/en/libraries/wifi/) and creating a `DNSServer`. ```cpp - #include + #include #include AsyncWebServer server(80); From 527f7106fb1dc76b55322490db0af396a6480f88 Mon Sep 17 00:00:00 2001 From: Rob Anderson Date: Tue, 13 Aug 2024 13:17:31 +0100 Subject: [PATCH 2/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c512ab4..e55d4db 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ScriptableThings -ScriptableThings is an Arduino library for building IoT devices that can easily be reprogrammed and reconfigured by administrators or users with standards-based JavaScript. It introduces a new malleable layer allowing dynamic scripting on devices, instead of functionality being baked into the firmware. The library enables you to create an API between your hardware and the scripting layer. We hope this makes IoT more maintainable over time, more resilient to vanishing cloud services and more-safely hackable devices. +ScriptableThings is an Arduino library for building IoT devices that can easily be reprogrammed and reconfigured by developers or users with standards-based JavaScript. It introduces a new malleable layer allowing dynamic scripting on devices, instead of functionality being baked into the firmware. The library enables you to create an API between your hardware and the scripting layer. We hope this makes IoT more maintainable over time, more resilient to vanishing cloud services and more-safely hackable devices. ## Dependencies From fe3743a1c633b61e3e203af0013aad82e6dcea69 Mon Sep 17 00:00:00 2001 From: Rob Anderson Date: Tue, 13 Aug 2024 14:07:33 +0100 Subject: [PATCH 3/7] update captive portal example --- examples/captive-portal/.vscode/extensions.json | 10 ++++++++++ examples/captive-portal/README.md | 11 +++++++++++ examples/captive-portal/data/www/index.html | 14 ++++++++++++++ examples/captive-portal/platformio.ini | 2 +- examples/captive-portal/src/main.cpp | 5 +---- 5 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 examples/captive-portal/.vscode/extensions.json create mode 100644 examples/captive-portal/README.md create mode 100644 examples/captive-portal/data/www/index.html diff --git a/examples/captive-portal/.vscode/extensions.json b/examples/captive-portal/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/examples/captive-portal/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/examples/captive-portal/README.md b/examples/captive-portal/README.md new file mode 100644 index 0000000..17045ac --- /dev/null +++ b/examples/captive-portal/README.md @@ -0,0 +1,11 @@ +# captive-portal + +This is a ScriptableThings example showing how to run a captive portal on the device. +The example uses PlatformIO to manage project dependencies. +Start with [src/main.cpp](./src/main.cpp). + +The device boots up, creates a `CaptivePortal` and hooks it up to the `AsyncWebServer` to serve the `www` directory inside the SPIFFS partition over HTTP. + +AsyncWebServer comes from [me-no-dev/ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) + +The `data` directory can be flashsed to the SPIFFS partition using PlatformIO's `Build Filesystem Image` and `Upload Filesystem Image` commands. diff --git a/examples/captive-portal/data/www/index.html b/examples/captive-portal/data/www/index.html new file mode 100644 index 0000000..0c9b7a1 --- /dev/null +++ b/examples/captive-portal/data/www/index.html @@ -0,0 +1,14 @@ + + + + + + Example Captive Portal + + +

Example Captive Portal

+

+ This is a captive portal being served by ScriptableThings +

+ + diff --git a/examples/captive-portal/platformio.ini b/examples/captive-portal/platformio.ini index 1fe4f3e..d2bb0e5 100644 --- a/examples/captive-portal/platformio.ini +++ b/examples/captive-portal/platformio.ini @@ -19,7 +19,7 @@ lib_deps = me-no-dev/AsyncTCP@^1.1.1 me-no-dev/ESPAsyncTCP@^1.2.2 https://github.com/me-no-dev/ESPAsyncWebServer.git#f71e3d427b5be9791a8a2c93cf8079792c3a9a26 - git@github.com:digitalinteraction/ScriptableThings.git#dd24789c5a52810b9b525c56ab515a2efd1cc05a + git@github.com:digitalinteraction/ScriptableThings.git#527f7106fb1dc76b55322490db0af396a6480f88 build_flags= -D ESP32 -D DEBUG diff --git a/examples/captive-portal/src/main.cpp b/examples/captive-portal/src/main.cpp index dd2fe04..ab2bd2e 100644 --- a/examples/captive-portal/src/main.cpp +++ b/examples/captive-portal/src/main.cpp @@ -5,10 +5,7 @@ #include auto server = AsyncWebServer(80); -auto portal = CaptivePortal("ProgThing Portal"); -auto engine = ProgramEngine(&SPIFFS, "/scripts/", ESP.getFreeHeap() >> 1, JavaScript::setup); - -AppContext *app; +auto portal = CaptivePortal("ScriptableThings Portal"); void setup() { From 5f62ff44a27e25f5329d6108ade9a5ad6bac009d Mon Sep 17 00:00:00 2001 From: Rob Anderson Date: Tue, 13 Aug 2024 15:23:07 +0100 Subject: [PATCH 4/7] add blink example --- examples/blink/.gitignore | 5 +++ examples/blink/.vscode/extensions.json | 10 ++++++ examples/blink/README.md | 11 ++++++ examples/blink/data/scripts/main.js | 8 +++++ examples/blink/include/README | 39 ++++++++++++++++++++++ examples/blink/lib/README | 46 ++++++++++++++++++++++++++ examples/blink/platformio.ini | 25 ++++++++++++++ examples/blink/src/JavaScript.h | 43 ++++++++++++++++++++++++ examples/blink/src/main.cpp | 29 ++++++++++++++++ examples/blink/test/README | 11 ++++++ 10 files changed, 227 insertions(+) create mode 100644 examples/blink/.gitignore create mode 100644 examples/blink/.vscode/extensions.json create mode 100644 examples/blink/README.md create mode 100644 examples/blink/data/scripts/main.js create mode 100644 examples/blink/include/README create mode 100644 examples/blink/lib/README create mode 100644 examples/blink/platformio.ini create mode 100644 examples/blink/src/JavaScript.h create mode 100644 examples/blink/src/main.cpp create mode 100644 examples/blink/test/README diff --git a/examples/blink/.gitignore b/examples/blink/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/examples/blink/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/examples/blink/.vscode/extensions.json b/examples/blink/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/examples/blink/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/examples/blink/README.md b/examples/blink/README.md new file mode 100644 index 0000000..426914b --- /dev/null +++ b/examples/blink/README.md @@ -0,0 +1,11 @@ +# blink + +This is a ScriptableThings example showing how to blink a pin from the JavaScript world. +It is based on the [Arduino Turorial](https://docs.arduino.cc/built-in-examples/basics/Blink/) +and uses PlatformIO to manage project dependencies. +Start with [src/main.cpp](./src/main.cpp). + +The device boots up, creates a `ProgramEngine` that looks inside the SPIFFS directory for JavaScript files +and configures it with the `JavaScript` setup method. When it boots up it runs [data/scripts/main.js](./data/scripts/main.js) . + +The `data` directory can be flashed to the SPIFFS partition using PlatformIO's `Build Filesystem Image` and `Upload Filesystem Image` commands. diff --git a/examples/blink/data/scripts/main.js b/examples/blink/data/scripts/main.js new file mode 100644 index 0000000..2e0bbbb --- /dev/null +++ b/examples/blink/data/scripts/main.js @@ -0,0 +1,8 @@ +let value = false; + +function loop() { + Thing.setLed(value); + value = !value; +} + +setInterval(loop, 1_000); diff --git a/examples/blink/include/README b/examples/blink/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/examples/blink/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/examples/blink/lib/README b/examples/blink/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/examples/blink/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/examples/blink/platformio.ini b/examples/blink/platformio.ini new file mode 100644 index 0000000..d2bb0e5 --- /dev/null +++ b/examples/blink/platformio.ini @@ -0,0 +1,25 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:featheresp32] +platform = espressif32 +board = featheresp32 +framework = arduino +monitor_speed = 115200 +lib_deps = + bblanchon/ArduinoJson@6.21.3 + bblanchon/StreamUtils@1.7.3 + me-no-dev/AsyncTCP@^1.1.1 + me-no-dev/ESPAsyncTCP@^1.2.2 + https://github.com/me-no-dev/ESPAsyncWebServer.git#f71e3d427b5be9791a8a2c93cf8079792c3a9a26 + git@github.com:digitalinteraction/ScriptableThings.git#527f7106fb1dc76b55322490db0af396a6480f88 +build_flags= + -D ESP32 + -D DEBUG diff --git a/examples/blink/src/JavaScript.h b/examples/blink/src/JavaScript.h new file mode 100644 index 0000000..b7e088b --- /dev/null +++ b/examples/blink/src/JavaScript.h @@ -0,0 +1,43 @@ +#include + +#define BLINK_PIN GPIO_NUM_25 + +class JavaScript +{ +public: + // + // This is called to customise the JavaScript runtime before script execution + // + static void setup(JSRuntime *rt, JSContext *ctx, JSValue global) + { + auto thing = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, global, "Thing", thing); + JS_SetPropertyStr(ctx, thing, "setLed", JS_NewCFunction(ctx, JavaScript::setLed, "setLed", 1)); + } + + // + // This is the API for scripts to talk to the firmware + // + // ```ts + // setLed(value: boolean): void + // ``` + // + static JSValue setLed(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv) + { + // Check the required variable is passed and throw a JS error if it was not + int value; + if (!JS_IsBool(argv[0])) + { + return JS_ThrowTypeError(ctx, "ERR_INVALID_ARG_TYPE"); + } + + // Get the argument from the JS world + value = JS_ToBool(ctx, argv[0]); + + // Set the LED based on the value called from JS + // Note - the pin number is controlled at the firmware level + digitalWrite(BLINK_PIN, value); + + return JS_UNDEFINED; + } +}; diff --git a/examples/blink/src/main.cpp b/examples/blink/src/main.cpp new file mode 100644 index 0000000..5286b3f --- /dev/null +++ b/examples/blink/src/main.cpp @@ -0,0 +1,29 @@ +#include +#include +#include + +#include "JavaScript.h" + +auto engine = ProgramEngine(&SPIFFS, "/scripts/", ESP.getFreeHeap() >> 1, JavaScript::setup); + +void setup() +{ + // Setup serial for log messages and mount the spiffs drive + Serial.begin(115200); + SPIFFS.begin(); + + // Configure the blink pin mode + pinMode(BLINK_PIN, OUTPUT); + + // Start up the JavaScript engine + engine.begin(); + + // Run the main script + engine.runScript("main.js"); +} + +void loop() +{ + // Tick the engine to process timers and etc + engine.loop(); +} diff --git a/examples/blink/test/README b/examples/blink/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/examples/blink/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html From aa305e115677b1ebaaaad56c50817a2b5f989d69 Mon Sep 17 00:00:00 2001 From: Rob Anderson Date: Tue, 25 Feb 2025 13:13:10 +0000 Subject: [PATCH 5/7] use http 511 for captive portal --- src/server/CaptiveWebHandler.cpp | 34 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/server/CaptiveWebHandler.cpp b/src/server/CaptiveWebHandler.cpp index 66977cd..6a360c6 100644 --- a/src/server/CaptiveWebHandler.cpp +++ b/src/server/CaptiveWebHandler.cpp @@ -15,20 +15,22 @@ void CaptiveWebHandler::handleRequest(AsyncWebServerRequest *request) Serial.println("-> redirect " + request->host() + request->url() + " -> " + hostname); - request->redirect(String("http://") + this->hostname); - - // auto location = "http://" + this->selfHostname; - // AsyncResponseStream *response = request->beginResponseStream("text/html"); - // response->setCode(511); - // response->print( - // "" - // "" - // "" - // "" - // "" - // "

" - // "Loading …

" - // "" - // ); - // request->send(response); + auto location = "http://" + hostname; + AsyncResponseStream *response = request->beginResponseStream("text/html"); + response->setCode(511); + response->print( + "" + "" + " " + " " + " Network Authentication Required" + " " + " " + " " + " " + "

You need to log in to access this network.

" + " " + "" + ); + request->send(response); } From 05f5b34fc1404f95ba7fd25744b6a66457ce03bb Mon Sep 17 00:00:00 2001 From: Rob Anderson Date: Wed, 19 Mar 2025 11:27:39 +0000 Subject: [PATCH 6/7] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index e55d4db..7bc8898 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,16 @@ In the future it would be interesting to explore more dynamic ways of getting sc There are more plans for HTTP features in the future, but for now there is just a CaptivePortal and you can use an AsyncWebServer yourself. See [examples/CaptivePortal](/examples/captive-portal/) for info on how to do that or the [AsyncWebServer docs](https://github.com/me-no-dev/ESPAsyncWebServer). +## Roadmap + +These are directions we'd like the library to go in from here + +- **Automated JavaScript interface generation** — Generate the c++ code that interfaces JavaScript and c-level code based on a documentation like [OpenAPI](https://swagger.io/specification/), currently this is [very manual](https://github.com/digitalinteraction/openlab-sign/blob/main/src/JavaScript.h). Possibly based one [ctags](https://github.com/universal-ctags/ctags) or [doxygen](https://www.doxygen.nl/index.html) +- **USB Host mode** — make the Arduino appear as a USB-drive when plugged into a computer so scripts can be loaded on that way +- **Integrated code editor** — A code editor within the device that can be served over HTTP to edit the scripts on the device itself, pulling in the auto-generated documentation / JavaScript API. Also a "changelog" like feature like a car's logbook +- **Simple configuration** — A way for an app to define it's user-configuration, an API to change those parameters and a web-app to serve for a user to change those parameters too. Potentially based on [JSONSchema](https://json-schema.org/) +- **WiFi YoYo** — A standardised API/UI to get an Arduino onto a WiFi network by first serving a captive portal where the user enters WiFi credentials + ## Useful links - [QuickJS docs](https://bellard.org/quickjs/quickjs.html) From 475d24e5a0e4061db81311ba569f3e7c4104f0da Mon Sep 17 00:00:00 2001 From: Rob Anderson Date: Tue, 17 Jun 2025 13:54:27 +0100 Subject: [PATCH 7/7] add notes + opt-in for IDE prettier --- .prettierrc | 1 + notes/01-captive-portals.md | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .prettierrc create mode 100644 notes/01-captive-portals.md diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/notes/01-captive-portals.md b/notes/01-captive-portals.md new file mode 100644 index 0000000..44a8899 --- /dev/null +++ b/notes/01-captive-portals.md @@ -0,0 +1,48 @@ +# Implementing Captive Portals on Ardiunos + +## Definition + +A Captive Portal is a WiFi network that captures the user's attention by +directing them to a website, rather than connecting directly to the internet. + +## Background + +The standard example Captive works by broadcasting an SSID and running a DHCP, DNS & HTTP server. +The DHCP server assigns clients an IP address and tells them to use the local DNS server. +The DNS server only ever returns the router's IP address for any request, +which traditionally prompts a device that this is a captive portal. +Devices will bring up a browser and visit that page, which is served by the HTTP server. + +Usually, [something like this](https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino) + +## Problems + +As of 2025, this method is no longer reliable on Android. +Captive Portals have also moved on with new specifications: + +- [RFC 7710](https://datatracker.ietf.org/doc/html/rfc7710) +- [RFC 8908](https://datatracker.ietf.org/doc/html/rfc8908) +- [RFC 8910](https://datatracker.ietf.org/doc/html/rfc8910) which superceeds `7710` + +The path forwards seems to be to use the DHCP option 114 to tell clients about the captive portal, +rather then using the DNS-hack. This method needs a configurable DHCP server that lets you set this option. +This DHCP server doesn't seem to exist yet. + +The Arduino DHCP server is not available to developers and doesn't let you configure how it works. + +> It might be worth investigating this [DHCP library](https://github.com/pkulchenko/DHCPLite) + +## Investigation + +- [Reddit thread describing option 114](https://www.reddit.com/r/esp32/comments/1h387rp/captive_portal_doesnt_show_pop_up/) +- [And another](https://www.reddit.com/r/networking/comments/t4webr/push_captive_portal_after_wifi_association/) +- A [very relevant GitHub issue](https://github.com/platformio/platform-espressif32/issues/1519) which I can't find any follow up or conencted issue in the `espressif/arduino-esp32` repository +- [Somewhat relevant issue about implementing DHCP over ethernet](https://github.com/espressif/esp-idf/issues/8886) + +From scanning the source code, it seems esp-idf doesn’t expose a way to configure which DHCP options to get/set, so re-compiling that might be necessary. + +- This isn’t the best direction either as it bakes in the requirement for using esp-idf rather than just Arduino +- The ideal option would be to propose an update to the Arduino WiFi standard that configures + +- A [similar request](https://github.com/esp8266/Arduino/issues/1956) for setting DHCP options in the Arduino wifi library +- [An old guide for creating a captive portal](https://iotespresso.com/create-captive-portal-using-esp32/)