Enhancing the Functionality of User Submitted Scripts in the PSLab-remote framework

The remote-lab framework of the pocket science lab enables users to access their devices remotely via the internet. Its design involves an API server built with Python-Flask and a webapp that uses EmberJS. This post is the latest in a series of blog posts which have explored and elaborated various aspect of the remote-lab such as designing the API server and testing with Postman, remote execution of function strings, automatic deployment on various domains etc. It also supports creating and submitting python scripts which will be run on the remote server, and the console output relayed to the webapp.

In this post, we shall take a look at how we can extend the functionality by providing support for object oriented code in user submitted scripts.

Let’s take an example of a Python script where the user wishes to create a button which when clicked will read a voltage via the API server, and display the value to the remote user. Clearly, an interpreter that only provides the console output is not enough for this task. We need the interpreter to generate an app structure that also includes callbacks for widgets such as buttons, and JSON objects are an obvious choice for relaying such a structure to the webapp.

In a nutshell, we had earlier created an API method that could execute a python script and return a string output, and now we will modify this method to return a JSON encoded structure which will be parsed by the webapp in order to display an output.

Let’s elaborate this with an example : Example.py

print ('testing')
print ('testing some changes..... ')
print_('highlighted print statement')

 

JSON returned by the API [localhost:8000/runScriptById] , for the above script:

{"Date": "Tue, 01 Aug 2017 21:39:12 GMT", "Filename": "example.py", "Id": 4,
 "result": [
  {"name": "print", "type": "span", "value": "('testing',)"},
  {"name": "print", "type": "span", "value": "('testing some changes..... ',)"},
  {"class": "row well", "name": "print", "type": "span", "value": "highlighted print statement"}
  ],
"status": true}
Screenshot of the EmberJS webapp showing the output rendered with the above JSON

Adding Support for Widgets

In the previous section, we laid the groundwork for a flexible platform. Instead of returning a string, the webapp accepts a JSON object and parses it. We shall now add support for a clickable button which can be associated with a valid PSLab function.

An elementary JS twiddle has been made by Niranjan Rajendran which will help newbies to understand how to render dynamic templates via JSON objects retrieved from APIs. The twiddle uses two API endpoints; one to retrieve the compiled JSON output, and another to act as a voltmeter method which returns a voltage value.

To understand how this works in pslab-remote, consider a one line script called button.py:

button('get voltage',"get_voltage('CH1')")

The objective is to create a button with the text ‘get voltage’ on it , and which when clicked will run the command ‘get_voltage(‘CH1’)’ on the API server, and display the result.

When this script is run on the API server, it returns a JSON object with the following structure:

{"Date": "Tue, 01 Aug 2017 21:39:12 GMT", "Filename": "example.py", "Id": 4,
 "result": [  {"type":"button","name":"button-id0","label":"get_voltage","fetched_value":"","action":{"type":"POST","endpoint":"get_voltage('CH1')","success":{"datapoint":'result',"type":"display_number", "target":"button-id0-label"}}},
  {"name": "button-id0label", "type": "label", "value": ""},
  ],
"status": true}

The above JSON object is parsed by the webapp’s user-home template, and a corresponding button and label are generated. The following section of code from user-home.hbs renders the JSON object

{{#each codeResults as |element|}}
  {{#if (eq element.type 'label')}}
    <label  id="{{element.name}}" class="{{element.class}}">{{element.value}}</label>
  {{/if}}
  {{#if (eq element.type 'button')}}
    <button id="{{element.name}}" {{action 'runButtonAction' element.action}}>{{element.label}}</button>
  {{/if}}
{{/each}}    

An action was also associated with the the created button, and this is the “get_voltage(‘CH1’)” string which we had specified in our one line script.

For the concluding section, we shall see how this action is invoked when the button is clicked, and how the returned value is used to update the contents of the label that was generated as part of this button.

Action defined in controllers/user-home.js :

runButtonAction(actionDefinition) {
  if(actionDefinition.type === 'POST') {
    Ember.$.post('/evalFunctionString',{'function':actionDefinition.endpoint},this,"json")
      .then(response => {
        const resultValue = Ember.get(response, actionDefinition.success.datapoint);
        if (actionDefinition.success.type === 'display_number') {
           Ember.$('#' + actionDefinition.success.target).text(resultValue.toFixed(3));
        }
      });
  }
}

The action string is passed to the evalFunctionString endpoint of the API, and the contents are mapped to the display label.

Screencast of the above process
Resources: