Skip to content

Commit

Permalink
Sample: event jsvm (#3)
Browse files Browse the repository at this point in the history
This sample demonstrates how to run your JavaScript application as performing some action
in response to an event, input or stimulus. 
Since Sming framework itself has an [event-driven architecture](https://sming.readthedocs.io/en/latest/information/events.html) such JavaScript applications will be able to use all the advantages that Sming has in terms of CPU, RAM and power usage.
  • Loading branch information
slaff authored Dec 2, 2021
1 parent bc6747b commit 7b02b40
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 11 deletions.
2 changes: 1 addition & 1 deletion JerryScriptUpdate.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Updating JerryScript
====================

highlight:: bash
.. highlight:: bash

Currently we use Jerryscript version v2.4 from `JerryScript <https://github.com/jerryscript-project/jerryscript>`__,
imported as a submodule.
Expand Down
12 changes: 3 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,12 @@ To save space and be able to run JerryScript on an embedded device Sming compile
This means that the JavaScript files have to be compiled before landing on the device.
See the samples below to learn more.

Samples
-------

- :sample:`Basic_Sample` - demonstrates how to use JerryScript and run compiled JavaScript code on a micro-controller.
- :sample:`Advanced_Sample` - demonstrates how to modify and compile JavaScript code on the fly using only your browser and run that code on a micro-controller.


Version
-------

See :ref:`JerryScriptUpdate` for version information.
.. toctree::

JerryScriptUpdate

Configuration variables
-----------------------
Expand Down Expand Up @@ -66,4 +60,4 @@ Credits
-------

The initial work on the JerryScript library for Sming was done as part of the `U:Kit project <https://github.com/attachix/ukit>`_.

2 changes: 1 addition & 1 deletion samples/Basic_Jsvm/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Basic_Jsvm
==========

.. highligh:: javascript
.. highlight:: javascript

This sample demonstrates running JavaScript applications in a sandbox inside a microcontroller.
The sample uses the :component:`jerryscript` library for Sming.
Expand Down
9 changes: 9 additions & 0 deletions samples/Event_Jsvm/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#####################################################################
#### Please don't change this file. Use component.mk instead ####
#####################################################################

ifndef SMING_HOME
$(error SMING_HOME is not set: please configure it as an environment variable)
endif

include $(SMING_HOME)/project.mk
66 changes: 66 additions & 0 deletions samples/Event_Jsvm/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
Event_Jsvm
==========

.. highlight:: cpp

This sample demonstrates how to run your JavaScript application as performing some action
in response to an event, input or stimulus.
Since Sming framework itself has an :doc:`event-driven architecture </information/events>`
such JavaScript applications will be able to use all the advantages that Sming has in terms of CPU, RAM and power usage.

JavaScript Code
---------------

Similar to Sming this sample calls the ``init`` JavaScript function. And in this function
we register the events that we are interested in. As shown below.

.. code:: javascript
/**
* This is a function that will be called once.
*/
function init() {
x = 0;
print('Init: X=' + x);
// This is how we register an event listener in JavaScript
addEventListener("EVENT_TEMP", function(event) {
// An event has a name and multiple params
// the params have string keys and string values
print('Event name: ' + event.name + ', value: ' + event.params['temp']);
});
// ...
}
The ``addEventListener`` mimics the browser `addEventListener <https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener>`_ function.
It accepts two parameters - a unique event name and a callable function. Similar to the browser function
you can add multiple callable functions to one event.  

C/C++ Code
----------

The ``addEventListener`` function is not present in the standard JerryScript VM. In this sample we register it as a built-in JavaScript function.
Excerpt from the ``application.cpp`` file::

void startJsvm()
{
/*
* This is how we register a new function in JavaScript
* that will communicate directly with our C/C++ code.
*/
jsVm.registerFunction("addEventListener", addEventListener);
// ...


The actual implementation of the ``addEventListener`` C++ function is in the ``vm_functions.cpp`` file.
There you will find also the ``triggerEvent`` function which is available only in the C/C++ code.
The latter is used to trigger events from the C/C++ code inside the JavaScript application::
JsEventData params;
params["temp"]="20";
triggerEvent("EVENT_TEMP", params);
The code above simulates a temperature sensor sending the temperature in Celsius (C).
49 changes: 49 additions & 0 deletions samples/Event_Jsvm/app/application.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <SmingCore.h>
#include <Jsvm.h>
#include <vm_functions.h>

namespace
{

Jsvm jsVm;
Timer tempTimer;

IMPORT_FSTR(main_snap, PROJECT_DIR "/out/jerryscript/main.js.snap")

void startJsvm()
{
/*
* This is how we register a new function in JavaScript
* that will communicate directly with our C/C++ code.
*/
jsVm.registerFunction("addEventListener", addEventListener);

if(!jsVm.load(main_snap)) {
debug_e("Failed to load snapshot");
return;
}

// Now you can initialize your script by calling the init() JavaScript function
if(!jsVm.runFunction("init")) {
debug_e("Failed executing the init function.");
}

/*
* Here we trigger every 2 seconds an event inside the JavaScript code.
*/
tempTimer.initializeMs(2000, TimerDelegate([](){
JsEventData params;
params["temp"]="20";
triggerEvent("EVENT_TEMP", params);
})).start();
}

} // namespace

void init()
{
Serial.begin(SERIAL_BAUD_RATE); // 115200 by default
Serial.systemDebugOutput(true); // Enable debug output to serial

startJsvm();
}
11 changes: 11 additions & 0 deletions samples/Event_Jsvm/component.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
COMPONENT_DEPENDS := jerryscript

COMPONENT_SRCDIRS += src
COMPONENT_INCDIRS += $(COMPONENT_SRCDIRS)/include

APP_JS_SOURCE_DIR := files
# APP_JS_SNAP_DIR := out/jerryscript
APP_JS_SNAP_UPDATED := touch app/application.cpp

# We need more heap to run this sample. The default heap is 1Kilobyte, we will use 2K
JERRY_GLOBAL_HEAP_SIZE := 2
31 changes: 31 additions & 0 deletions samples/Event_Jsvm/files/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
var x;

function thirdListener(event) {
print('Third listener is a normal function!');
print('-------------' + (++x) + '------------------');
}

/**
* This is a function that will be called once.
*/
function init() {
x = 0;
print('Init: X=' + x);

// This is how we register an event listener in JavaScript
addEventListener("EVENT_TEMP", function(event) {
// An event has a name and multiple params
// the params have string keys and string values
print('Event name: ' + event.name + ', value: ' + event.params['temp']);
});

addEventListener("EVENT_TEMP", function(event) {
// You can register more than one listener to an event
print('Second listener');
});

// You can also run a normal function as callback instead of
// an anonymous function.
addEventListener("EVENT_TEMP", thirdListener);
}

45 changes: 45 additions & 0 deletions samples/Event_Jsvm/src/include/vm_functions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* jsvm-ext.cpp
*
* @author Nov 2021 - Slavey Karadzhov <slav@attachix.com>
*/

#pragma once

#include <jerryscript.h>
#include <WHashMap.h>
#include <WString.h>

using JsEventData = HashMap<String, String>;

void triggerEvent(const String& name, const JsEventData& data);

/**
* External functions that are available to JerryScript
*/

/**
* @brief Function to register event listeners
* @code {.javascipt}
*
* event = {
* name => "TEMP_CHANGE",
* params = {
* },
* "origin" => "The\Creator\Of\The\Event"
* };
*
* addEventListener("TEMP_CHANGE", function(event) {
* console.log("Got Event" + event.name);
* });
*
* @endcode
*
*/
jerry_value_t addEventListener(const jerry_value_t function_obj, const jerry_value_t this_val,
const jerry_value_t args_p[], const jerry_length_t args_cnt);
112 changes: 112 additions & 0 deletions samples/Event_Jsvm/src/vm_functions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#include "include/vm_functions.h"

#include <WVector.h>
#include <debug_progmem.h>

namespace
{
using EventList = Vector<jerry_value_t>;
using Events = HashMap<String, EventList>;

Events events;

} // namespace

jerry_value_t addEventListener(const jerry_value_t function_obj, const jerry_value_t this_val,
const jerry_value_t args_p[], const jerry_length_t args_cnt)
{
(void)function_obj; /* unused */
(void)this_val; /* unused */

if(args_cnt != 2) {
return jerry_create_boolean(false);
}

// First Parameter - Event Name
jerry_value_t value = jerry_value_to_string(args_p[0]);

jerry_size_t valueSize = jerry_get_string_size(value);
jerry_char_t eventName[valueSize + 1];

jerry_string_to_char_buffer(value, eventName, valueSize);
eventName[valueSize] = '\0';

jerry_release_value(value);

// Second parameter -> callable
auto jsFunction = args_p[1];

if(!jerry_value_is_function(jsFunction)) {
debug_e("Second parameter is not a function!");

return jerry_create_boolean(false);
}

events[reinterpret_cast<char*>(eventName)].addElement(jerry_value_to_object(jsFunction));

return jerry_create_boolean(true);
}

void triggerEvent(const String& name, const JsEventData& data)
{
if(!events.contains(name)) {
return;
}

/*
* Event = {
* name: "eventName"
* params: {
* "property1": "value1",
* "property2": "value2"
* ...
* }
* }
*/

// Build the event object...
jerry_value_t eventObject = jerry_create_object();
// Name
jerry_value_t namePropertyName = jerry_create_string((const jerry_char_t*)"name");
jerry_value_t namePropertyValue = jerry_create_string((const jerry_char_t*)name.c_str());
jerry_value_t nameProperty = jerry_set_property(eventObject, namePropertyName, namePropertyValue);
// Params
jerry_value_t paramsPropertyName = jerry_create_string((const jerry_char_t*)"params");
jerry_value_t paramsPropertyValue = jerry_create_object();
jerry_value_t paramsProperty = jerry_set_property(eventObject, paramsPropertyName, paramsPropertyValue);

// add params...
Vector<jerry_value_t> props;
for(unsigned i = 0; i < data.count(); i++) {
jerry_value_t key = jerry_create_string(reinterpret_cast<const jerry_char_t*>(data.keyAt(i).c_str()));
jerry_value_t value = jerry_create_string(reinterpret_cast<const jerry_char_t*>(data.valueAt(i).c_str()));
jerry_value_t property = jerry_set_property(paramsPropertyValue, key, value);
props.add(key);
props.add(value);
props.add(property);
}

jerry_value_t globalObject = jerry_get_global_object();

EventList listeners = events[name];
for(unsigned i = 0; i < listeners.count(); i++) {
jerry_value_t res = jerry_call_function(listeners[i], globalObject, &eventObject, 1);
jerry_release_value(res);
}

jerry_release_value(globalObject);

for(unsigned i = 0; i < props.count(); i++) {
jerry_release_value(props[i]);
}

jerry_release_value(namePropertyName);
jerry_release_value(namePropertyValue);
jerry_release_value(nameProperty);

jerry_release_value(paramsPropertyName);
jerry_release_value(paramsPropertyValue);
jerry_release_value(paramsProperty);

jerry_release_value(eventObject);
}

0 comments on commit 7b02b40

Please sign in to comment.