Skip to content

Guiding Light

Initialize a new project and start to interact with some hardware!

Project creation

The init command will create a new directory with the name provided and scaffold the starting files based on a template or example:

xs-dev init guiding-light

The above command should result in the following output:

Generating Moddable project: guiding-light
Run the project using: cd guiding-light && xs-dev run

The guiding-light directory should contain main.js and manifest.json files. main.js contains that was run from the Hello Console example:

debugger;
let message = "Hello, world - sample";
trace(message);

The first line is a debugger statement for setting a breakpoint in xsbug. The third and fourth lines save a string to a variable and log it to the xsbug console using the global trace function.

The Moddable docs describe the manifest.json as follows:

A manifest is a JSON file that describes the modules and resources necessary to build a Moddable app.

The initialized manifest.json in the guiding-light project should look like this:

{
"include": [
"$(MODDABLE)/examples/manifest_base.json"
],
"modules": {
"*": "./main"
}
}

The include field contains references to other manifests to provide quick reuse of common configuration found in the Moddable SDK, examples, and your own projects. The manifest_base.json includes basic platform support for all available platforms and some initial modules for time, timers, and instrumentation.

The modules field should contain a mapping of every module to include in the build. The * key means the module (or list of modules) can be imported and referenced by their file name. A custom key can be used as an alias to reference when importing the assigned module.

Executing xs-dev run should provide the same experience as the Hello Console guide.

Quick tip: check out all the available simulators by using the --list-devices flag with the run command and typing “simulator” to filter the list.

Down to the metal

At this point, we have our chosen hardware in hand and need to set up the dev environment to start running code on the device.

Just like the previous step, the setup command will automate the installation and building of tooling required for the target device. The --list-devices flag will provide an interactive list of supported device platforms:

❯ xs-dev setup --list-devices
? Here are the available target devices: …
esp8266
esp32
pico
wasm
mac

You may see different options depending on what operating system or version of xs-dev you are using.

Once this process is done, you should see a success message (where <device> is the selected target device):

Successfully set up <device> platform support for Moddable!
Test out the setup by starting a new terminal session, plugging in your device, and running: xs-dev run --example helloworld --device=<device>
If there is trouble finding the correct port, pass the "--port" flag to the above command with the path to the /dev.cu.* that matches your device.

Running our project on the selected device (which should be connected to the computer somehow, presumable over USB) is the same command as before with the additional --device flag to pass in the target device platform:

xs-dev run --device <device>

This will take some time to compile and send the code over to the device. When it has succeeded, the debugger will open like before but now it is tracing the logs coming from the hardware!

👏 Give yourself a round of applause! You have now run JavaScript on an embedded device! 🎉

Hello blinky

Now that we know we can run code on our device, it is time to shed a little light on hardware control. We will use the ECMA-419 standard APIs to perform this task. To access those APIs we need to include them in our project:

xs-dev include io

The include command updates the manifest.json to (you guessed it) include the required module(s) from the Moddable SDK. In this case, the io module provides the complete set of ECMA-419 APIs for the supported device platform. The manifest.json should look like this:

{
"include": [
"$(MODDABLE)/examples/manifest_base.json",
"$(MODDABLE)/modules/io/manifest.json"
],
"modules": {
"*": "./main"
}
}

With that configured, the main.js file can be updated with the following code:

const Digital = device.io.Digital;
const led = new Digital({
pin: device.pin.led,
mode: Digital.Output,
});
led.write(1);
let state = 0;
System.setInterval(() => {
led.write(state);
if (state === 0) {
state = 1;
} else {
state = 0;
}
}, 200);

Using the global device variable provided by the io module, we can access the Digital IO class for controlling the digital output to an LED. In this example, the Digital class is instantiated with the pin property set to the built-in led as defined on the global device and the mode set to the Digital.Output static property found on the class. With that Digital instance variable called led, the write method is called with a value of 1 to send power to the LED.

const Digital = device.io.Digital;
const led = new Digital({
pin: device.pin.led,
mode: Digital.Output,
});
led.write(1);

To make the light blink, the next value to be written is stored as the state variable. The global System class provides the well-known setInterval function that is found in other JavaScript runtimes like the Web and Node.js. Every 200 milliseconds, the state is written to the LED before being updated to the opposite value.

let state = 0;
System.setInterval(() => {
led.write(state);
if (state === 0) {
state = 1;
} else {
state = 0;
}
}, 200);

The project can be run using the same command as before: xs-dev run --device <device>. If it succeeds, you should see a blinking LED somewhere on your device! ✨

Keep exploring!

Tried adding some trace calls to log the state to the debugger or updating the timer code to send a message in Morse code.

Coming soon: react to digital input by pressing some buttons

In the meantime, check out the many examples available in the Moddable SDK.

Troubleshooting

If you’re working with a device that doesn’t have an on-board LED or encounter an error while trying to use the device.pin.led value, the pin specifier can be set to a custom value based on the device datasheet or pinout diagram, like this one for the Pico. The pin value can match the on-board LED or an external LED connected to a GPIO, most likely by using a breadboard.